Skip to main content

Kubernetes Deployment

Deploy ArgusAI to a Kubernetes cluster using raw manifests for fine-grained control.

Alternative Options

Prerequisites

  • Kubernetes cluster 1.25+ (local: minikube, kind, Docker Desktop; cloud: EKS, GKE, AKS)
  • kubectl configured with cluster access
  • Container registry access to ghcr.io (or your own registry)

Quick Start

# Clone the repository
git clone https://github.com/project-argusai/ArgusAI.git
cd ArgusAI

# Create namespace
kubectl create namespace argusai

# Generate and apply secrets
kubectl create secret generic argusai-secrets \
--namespace argusai \
--from-literal=ENCRYPTION_KEY=$(python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())") \
--from-literal=JWT_SECRET_KEY=$(openssl rand -hex 32)

# Apply all manifests
kubectl apply -f k8s/ -n argusai

# Watch pods come up
kubectl get pods -n argusai -w

Manifest Overview

ArgusAI provides the following Kubernetes manifests in the k8s/ directory:

ManifestPurpose
backend-deployment.yamlBackend FastAPI application
frontend-deployment.yamlFrontend Next.js application
backend-service.yamlClusterIP service for backend
frontend-service.yamlClusterIP service for frontend
configmap.yamlNon-sensitive configuration
secret.yamlTemplate for sensitive values
pvc.yamlPersistent storage for data
ingress.yamlOptional ingress for external access

Architecture

                  ┌─────────────────────────────────────┐
│ Ingress │
│ (nginx-ingress or cloud LB) │
└──────────────┬──────────────────────┘

┌──────────────────────┼──────────────────────┐
│ │ │
▼ ▼ │
┌─────────────────┐ ┌─────────────────┐ │
│ frontend-svc │ │ backend-svc │ │
│ ClusterIP:3000 │ │ ClusterIP:8000 │ │
└────────┬────────┘ └────────┬────────┘ │
│ │ │
▼ ▼ │
┌─────────────────┐ ┌─────────────────┐ │
│ Frontend Pod │ │ Backend Pod │ │
│ Next.js │ │ FastAPI │ │
└─────────────────┘ └────────┬────────┘ │
│ │
▼ │
┌─────────────────┐ │
│ PVC: argusai- │ │
│ data (10Gi) │ │
└─────────────────┘ │

Configuration

ConfigMap

The ConfigMap contains non-sensitive configuration:

apiVersion: v1
kind: ConfigMap
metadata:
name: argusai-config
data:
DEBUG: "false"
LOG_LEVEL: "INFO"
CORS_ORIGINS: "http://localhost:3000"
DATABASE_URL: "sqlite:///data/app.db"

Customize these values by editing k8s/configmap.yaml before applying.

Secrets

Secrets contain sensitive configuration. Never commit actual secrets to git.

# Create secrets imperatively (recommended)
kubectl create secret generic argusai-secrets \
--namespace argusai \
--from-literal=ENCRYPTION_KEY=your-fernet-key \
--from-literal=JWT_SECRET_KEY=your-jwt-secret

# Or from files
kubectl create secret generic argusai-secrets \
--namespace argusai \
--from-file=ENCRYPTION_KEY=./encryption.key \
--from-file=JWT_SECRET_KEY=./jwt.key

Generate Required Keys

# Generate Fernet encryption key
python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"

# Generate JWT secret key
openssl rand -hex 32

Persistent Volume

The PVC provides persistent storage for:

  • SQLite database
  • Event thumbnails
  • Video frames
  • SSL certificates
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: argusai-data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
Storage Class

The default manifest uses the cluster's default StorageClass. For production, specify an appropriate StorageClass:

spec:
storageClassName: "fast-ssd" # Your storage class

Scaling

Horizontal Scaling

# Scale frontend (stateless)
kubectl scale deployment argusai-frontend --replicas=3 -n argusai

# Note: Backend requires careful scaling due to SQLite
# For multi-replica backend, use PostgreSQL

Resource Limits

Default resource configuration:

ComponentCPU RequestCPU LimitMemory RequestMemory Limit
Backend250m1000m512Mi1Gi
Frontend100m500m256Mi512Mi

Adjust in deployment manifests based on your workload:

resources:
requests:
memory: "1Gi"
cpu: "500m"
limits:
memory: "2Gi"
cpu: "2000m"

Ingress Configuration

The included ingress manifest works with nginx-ingress controller:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: argusai-ingress
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
spec:
ingressClassName: nginx
rules:
- host: argusai.example.com
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: argusai-backend
port:
number: 8000
- path: /
pathType: Prefix
backend:
service:
name: argusai-frontend
port:
number: 3000

Enable TLS

spec:
tls:
- hosts:
- argusai.example.com
secretName: argusai-tls
rules:
# ... your rules

Create the TLS secret:

kubectl create secret tls argusai-tls \
--namespace argusai \
--cert=path/to/cert.pem \
--key=path/to/key.pem

Or use cert-manager for automatic certificate management:

metadata:
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"

Using PostgreSQL

For production deployments, use PostgreSQL instead of SQLite:

Deploy PostgreSQL

# Using Bitnami Helm chart (recommended)
helm install postgres bitnami/postgresql \
--namespace argusai \
--set auth.database=argusai \
--set auth.username=argusai \
--set auth.password=your-secure-password

Update ConfigMap

data:
DATABASE_URL: "postgresql://argusai:your-password@postgres-postgresql:5432/argusai"

Health Checks

Both deployments include liveness and readiness probes:

# Backend
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 30
periodSeconds: 10

readinessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 5
periodSeconds: 5

# Frontend
livenessProbe:
httpGet:
path: /
port: 3000
initialDelaySeconds: 30
periodSeconds: 10

readinessProbe:
httpGet:
path: /
port: 3000
initialDelaySeconds: 5
periodSeconds: 5

Security

Non-Root Containers

All pods run as non-root user (UID 1000):

securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000

Network Policies (Optional)

Restrict pod-to-pod communication:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: argusai-backend-policy
namespace: argusai
spec:
podSelector:
matchLabels:
app: argusai
component: backend
ingress:
- from:
- podSelector:
matchLabels:
app: argusai
component: frontend
ports:
- port: 8000

Monitoring

View Logs

# Backend logs
kubectl logs -l app=argusai,component=backend -n argusai -f

# Frontend logs
kubectl logs -l app=argusai,component=frontend -n argusai -f

# All ArgusAI logs
kubectl logs -l app=argusai -n argusai -f

Check Pod Status

# Pod status
kubectl get pods -n argusai

# Detailed pod info
kubectl describe pod -l app=argusai -n argusai

# Events
kubectl get events -n argusai --sort-by='.lastTimestamp'

Prometheus Metrics

The backend exposes Prometheus metrics at /metrics:

# Example ServiceMonitor for Prometheus Operator
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: argusai-backend
namespace: argusai
spec:
selector:
matchLabels:
app: argusai
component: backend
endpoints:
- port: http
path: /metrics

Troubleshooting

Pod Won't Start

# Check pod events
kubectl describe pod <pod-name> -n argusai

# Common issues:
# - ImagePullBackOff: Check registry access
# - CrashLoopBackOff: Check logs and secrets
# - Pending: Check resource availability

Database Issues

# Exec into backend pod
kubectl exec -it deployment/argusai-backend -n argusai -- /bin/sh

# Check database file
ls -la /app/data/

# Run migrations manually
alembic upgrade head

Storage Issues

# Check PVC status
kubectl get pvc -n argusai

# Describe PVC
kubectl describe pvc argusai-data -n argusai

Uninstalling

# Remove all resources (keeps PVC)
kubectl delete -f k8s/ -n argusai

# Remove namespace and everything
kubectl delete namespace argusai

# Note: PVC deletion requires explicit deletion
kubectl delete pvc argusai-data -n argusai

Next Steps