Self-Hosted Deployment
Deploy SessionFS on your own Kubernetes cluster.
Overview
Section titled “Overview”SessionFS can be deployed to any Kubernetes cluster using the official Helm chart. The deployment includes:
- API Server — FastAPI application handling session CRUD, sync, and authentication
- MCP Server — Model Context Protocol bridge (optional)
- Web Dashboard — React management interface (optional)
- PostgreSQL — Built-in or external database
- Blob Storage — Local PVC, Amazon S3, or Google Cloud Storage
Prerequisites
Section titled “Prerequisites”- Kubernetes 1.26 or later
- Helm 3.12 or later
kubectlconfigured for your cluster- A PersistentVolume provisioner (most managed clusters include one)
Security Posture
Section titled “Security Posture”SessionFS ships with a hardened default security posture — no action required on your part. The Helm chart and container images meet CIS Kubernetes benchmark and Pod Security Standard restricted profile.
Container images
Section titled “Container images”Both sessionfs-api and sessionfs-mcp Docker images:
- Run as non-root user (UID 10001, dedicated
sessionfssystem user) - Ship with no shell or package manager for the runtime user
- Are scanned on every release via
trivy(CRITICAL/HIGH findings block the pipeline)
Kubernetes SecurityContext
Section titled “Kubernetes SecurityContext”Every pod declared by the chart — API, MCP, dashboard, PostgreSQL, and the helm test hook — runs with:
| Setting | Value |
|---|---|
runAsNonRoot | true |
runAsUser | 10001 (or 999 for PostgreSQL, matching upstream convention) |
readOnlyRootFilesystem | true |
allowPrivilegeEscalation | false |
capabilities.drop | [ALL] |
seccompProfile.type | RuntimeDefault |
The PostgreSQL container mounts emptyDir volumes at /tmp and /var/run/postgresql so it can write its socket directory and temp files even with a read-only root filesystem. The persistent data volume (/var/lib/postgresql/data) uses a standard PVC.
Network policies
Section titled “Network policies”The chart does not ship NetworkPolicies by default so the chart works on clusters without a CNI that supports them. To apply restrictive NetworkPolicies:
networkPolicy: enabled: true # Coming in a future chart releaseFor now, enforce network isolation at the namespace level via your CNI (Cilium, Calico, or similar).
Secrets
Section titled “Secrets”All secrets (database credentials, verification secret, encryption key, SMTP credentials, Resend API key) live in Kubernetes Secret objects, never in ConfigMaps. See Secrets Management below.
Verification
Section titled “Verification”You can verify the security posture of a rendered chart with trivy:
helm template sessionfs sessionfs/sessionfs \ --namespace sessionfs \ > /tmp/rendered.yaml
trivy config /tmp/rendered.yaml --severity CRITICAL,HIGHA clean scan should report zero CRITICAL or HIGH misconfigurations.
Installation
Section titled “Installation”1. Add the Helm Repository
Section titled “1. Add the Helm Repository”helm repo add sessionfs https://charts.sessionfs.devhelm repo update2. Create a Namespace
Section titled “2. Create a Namespace”kubectl create namespace sessionfs3. Choose Your Configuration
Section titled “3. Choose Your Configuration”Minimal (development / evaluation)
Section titled “Minimal (development / evaluation)”Single replica, built-in PostgreSQL, no ingress:
helm install sessionfs sessionfs/sessionfs \ -f values.minimal.yaml \ --namespace sessionfsAccess via port-forward:
kubectl port-forward svc/sessionfs-api 8000:8000 -n sessionfsStandard
Section titled “Standard”Two API replicas, built-in PostgreSQL, ingress enabled:
helm install sessionfs sessionfs/sessionfs \ --namespace sessionfs \ --set ingress.hosts[0].host=sessionfs.yourdomain.comProduction
Section titled “Production”External database, cloud storage, autoscaling, network policies:
helm install sessionfs sessionfs/sessionfs \ -f values.production.yaml \ --namespace sessionfs \ --set postgresql.enabled=false \ --set externalDatabase.existingSecret=sessionfs-db \ --set security.existingSecret=sessionfs-secrets \ --set storage.type=s3 \ --set storage.s3.bucket=my-sessionfs-bucket \ --set ingress.enabled=true \ --set ingress.className=nginx \ --set ingress.hosts[0].host=sessionfs.yourdomain.com \ --set ingress.hosts[0].paths.api=/api \ --set ingress.hosts[0].paths.mcp=/mcp \ --set ingress.hosts[0].paths.dashboard=/Secrets Management
Section titled “Secrets Management”SessionFS requires several secrets for operation. You can either provide them inline in values.yaml (not recommended for production) or reference pre-existing Kubernetes secrets.
Create Secrets Manually
Section titled “Create Secrets Manually”# Application secretskubectl create secret generic sessionfs-secrets \ --namespace sessionfs \ --from-literal=verification-secret="$(openssl rand -hex 32)" \ --from-literal=encryption-key="$(openssl rand -hex 32)" \ --from-literal=resend-api-key="re_your_key_here"
# External database (if not using built-in PostgreSQL)kubectl create secret generic sessionfs-db \ --namespace sessionfs \ --from-literal=database-url="postgresql+asyncpg://user:pass@host:5432/sessionfs"Then reference them in your Helm values:
security: existingSecret: sessionfs-secretsexternalDatabase: existingSecret: sessionfs-dbStorage Configuration
Section titled “Storage Configuration”Local (default)
Section titled “Local (default)”Uses a PersistentVolumeClaim. Suitable for single-node clusters or evaluation.
storage: type: local local: persistence: enabled: true size: 10GiAmazon S3
Section titled “Amazon S3”storage: type: s3 s3: bucket: my-sessionfs-bucket # Bucket name only — no slashes region: us-east-1 prefix: "" # Optional key prefix (e.g. "sessionfs/")Using IRSA (IAM Roles for Service Accounts):
If your EKS nodes use IRSA, no additional credentials are needed. Attach this IAM policy to the service account role:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:GetObject", "s3:PutObject", "s3:DeleteObject", "s3:ListBucket" ], "Resource": [ "arn:aws:s3:::my-sessionfs-bucket", "arn:aws:s3:::my-sessionfs-bucket/*" ] } ]}Annotate the service account in your Helm values:
serviceAccount: create: true annotations: eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/sessionfs-s3-roleUsing static credentials:
kubectl create secret generic aws-creds \ --namespace sessionfs \ --from-literal=aws-access-key-id=AKIA... \ --from-literal=aws-secret-access-key=...storage: s3: existingSecret: aws-credsGoogle Cloud Storage
Section titled “Google Cloud Storage”storage: type: gcs gcs: bucket: my-sessionfs-bucketIf using Workload Identity, no additional credentials are needed. Otherwise:
kubectl create secret generic gcs-creds \ --namespace sessionfs \ --from-file=gcs-credentials-json=./sa-key.jsonstorage: gcs: existingSecret: gcs-credsDatabase Configuration
Section titled “Database Configuration”Built-in PostgreSQL (default)
Section titled “Built-in PostgreSQL (default)”The chart deploys a single-replica PostgreSQL StatefulSet. Suitable for small deployments.
postgresql: enabled: true auth: username: sessionfs database: sessionfs persistence: size: 10GiExternal Database
Section titled “External Database”For production, use a managed PostgreSQL service (AWS RDS, GCP Cloud SQL, Azure Database for PostgreSQL).
postgresql: enabled: false
externalDatabase: host: your-db-host.region.rds.amazonaws.com port: 5432 username: sessionfs database: sessionfs existingSecret: sessionfs-dbImportant: Do NOT add sslMode or ?sslmode=require to the connection URL. SessionFS handles SSL negotiation automatically via asyncpg. For RDS and Cloud SQL, SSL is negotiated transparently for non-localhost connections.
TLS / HTTPS
Section titled “TLS / HTTPS”Configure TLS through your ingress controller. Example with cert-manager:
ingress: enabled: true className: nginx annotations: cert-manager.io/cluster-issuer: letsencrypt-prod hosts: - host: sessionfs.yourdomain.com paths: api: /api mcp: /mcp dashboard: / tls: - secretName: sessionfs-tls hosts: - sessionfs.yourdomain.comMonitoring
Section titled “Monitoring”Enable Prometheus ServiceMonitor (requires prometheus-operator):
monitoring: serviceMonitor: enabled: true interval: 30s labels: release: prometheusUpgrading
Section titled “Upgrading”helm repo updatehelm upgrade sessionfs sessionfs/sessionfs \ --namespace sessionfs \ --reuse-valuesDatabase migrations run automatically as a Helm post-upgrade hook.
Troubleshooting
Section titled “Troubleshooting”Check pod status
Section titled “Check pod status”kubectl get pods -n sessionfskubectl describe pod <pod-name> -n sessionfsView API logs
Section titled “View API logs”kubectl logs -n sessionfs -l app.kubernetes.io/component=api --tail=100Check migration job
Section titled “Check migration job”kubectl get jobs -n sessionfs -l app.kubernetes.io/component=migrationkubectl logs -n sessionfs job/sessionfs-migrate-<revision>Run Helm tests
Section titled “Run Helm tests”helm test sessionfs --namespace sessionfsCommon Issues
Section titled “Common Issues”Pods stuck in Pending: Check that your cluster has a PersistentVolume provisioner and sufficient resources.
Database connection errors: Verify the database URL and credentials. For external databases, ensure network connectivity (security groups, VPC peering).
Ingress not working: Confirm your ingress controller is installed and the ingress class name matches.
Security context errors (runAsNonRoot): SessionFS images currently run as root. Set security contexts in values.yaml:
api: podSecurityContext: runAsNonRoot: falseasyncpg SSL errors: Do not add ?sslmode=require to the database URL — SessionFS handles SSL parameter translation internally. For RDS/Cloud SQL, asyncpg negotiates SSL automatically for non-localhost connections.
API Documentation
Section titled “API Documentation”After deployment, the API is documented via OpenAPI:
- Swagger UI:
https://your-domain/api/docs - ReDoc:
https://your-domain/api/redoc - OpenAPI JSON:
https://your-domain/api/openapi.json
Key endpoints:
| Endpoint | Description |
|---|---|
POST /api/v1/auth/signup | Create account |
GET /api/v1/auth/me | Current user profile |
GET /api/v1/sessions | List sessions |
POST /api/v1/sessions/{id}/audit | Run LLM Judge |
GET /health | Health check |
Email Configuration
Section titled “Email Configuration”SessionFS supports three email providers: Resend (SaaS), SMTP (enterprise), or none (air-gapped).
Resend (default for cloud)
Section titled “Resend (default for cloud)”helm install sessionfs sessionfs/sessionfs \ --set email.provider=resend \ --set email.resend.apiKey=$RESEND_KEYSMTP (enterprise / internal)
Section titled “SMTP (enterprise / internal)”helm install sessionfs sessionfs/sessionfs \ --set email.provider=smtp \ --set email.smtp.host=smtp.company.internal \ --set email.smtp.port=587 \ --set email.smtp.username=sessionfs \ --set email.smtp.password=$SMTP_PASS \ --set email.fromAddress=sessionfs@company.comFor implicit SSL (port 465):
--set email.smtp.port=465 \ --set email.smtp.ssl=true \ --set email.smtp.tls=falseNo email (air-gapped)
Section titled “No email (air-gapped)”helm install sessionfs sessionfs/sessionfs \ --set email.provider=none \ --set api.env.SFS_REQUIRE_EMAIL_VERIFICATION=falseUsers will be auto-verified on signup. Email notifications (handoff, retention) will be logged but not sent.
SMTP with Kubernetes Secrets
Section titled “SMTP with Kubernetes Secrets”kubectl create secret generic smtp-creds \ --namespace sessionfs \ --from-literal=username=sessionfs \ --from-literal=password=$SMTP_PASS
helm install sessionfs sessionfs/sessionfs \ --set email.provider=smtp \ --set email.smtp.host=smtp.company.internal \ --set email.smtp.existingSecret=smtp-credsDatabase Migrations
Section titled “Database Migrations”Automatic (Helm)
Section titled “Automatic (Helm)”Migrations run automatically on helm install and helm upgrade via a post-install/post-upgrade hook. The migration job runs before the API pods start (hook weight -5).
Manual
Section titled “Manual”kubectl exec -it deploy/sessionfs-api -- alembic upgrade headTroubleshooting Migrations
Section titled “Troubleshooting Migrations”If the migration job fails, check logs:
kubectl logs job/sessionfs-migrate-<revision> -n sessionfsThe API will fail to start if tables don’t exist. Ensure migrations complete first. The Helm hook handles ordering automatically.
Rate Limiting
Section titled “Rate Limiting”Rate limiting is per API key, defaulting to 120 requests per minute.
api: rateLimitPerMinute: 120 # Requests per minute per API key # Set to 0 to disable rate limiting (recommended for internal deployments)Or via environment variable:
api: env: SFS_RATE_LIMIT_PER_MINUTE: "0" # Disable for internal deploymentsArchitecture
Section titled “Architecture”SessionFS uses a reverse proxy architecture. All external traffic routes through the dashboard nginx, which proxies /api/ and /mcp/ to internal ClusterIP services:
Internet -> Ingress -> Dashboard Nginx -> API Service (ClusterIP) -> MCP Service (ClusterIP) -> Static FilesThe ingress template routes everything to the dashboard service. Do NOT create separate ingress rules for /api or /mcp — this causes ALB target groups to stay in “unused” state.
Upload size limit
Section titled “Upload size limit”The dashboard nginx has client_max_body_size configured (default 100MB). Override in values.yaml:
dashboard: clientMaxBodySize: "200m"GitLab Integration
Section titled “GitLab Integration”SessionFS supports GitLab merge request comments (cloud and self-hosted instances).
- Create a GitLab personal or project access token with
apiscope - Add a webhook to your GitLab project:
- URL:
https://your-sessionfs-domain/webhooks/gitlab - Secret token: set in your Helm values or
SFS_GITLAB_WEBHOOK_SECRETenv var - Events: “Merge request events”
- URL:
- Configure in the dashboard Settings page or via Helm:
api: env: SFS_GITLAB_WEBHOOK_SECRET: "your-webhook-secret"GitLab MR comments include the same AI session context as GitHub PR comments, including audit findings when available.
Environment Variables
Section titled “Environment Variables”See Environment Variables Reference for the complete list of SFS_* configuration options.
Further Troubleshooting
Section titled “Further Troubleshooting”See Troubleshooting Guide for common errors, sync debugging, and Kubernetes-specific issues.