This guide walks through deploying Frona on Kubernetes. All three containers (Frona, Browserless, SearXNG) run in a single pod and communicate via localhost.
Example manifests are available in the repository at examples/kubernetes/.
Prerequisites
- A Kubernetes cluster (1.25+)
kubectlconfigured to access your cluster
Quick start
# 1. Clone the repository and navigate to the examples
git clone https://github.com/fronalabs/frona.git
cd frona/examples/kubernetes
# 2. Deploy all resources
kubectl apply -k .
# 3. Wait for the pod to be ready
kubectl -n frona get pods -w
# 4. Access Frona (port-forward for local testing)
kubectl -n frona port-forward svc/frona 3001:3001
open http://localhost:3001Architecture
All three containers run in a single pod:
| Container | Description |
|---|---|
| frona | Frona server (port 3001) |
| browserless | Headless Chromium for browser automation |
| searxng | Meta search engine for web search |
Because they share a pod, Frona connects to Browserless and SearXNG via localhost.
Manifests
Persistent storage
Frona needs persistent storage for its database, workspaces, files, and browser profiles. A single volume mounted at /app/data covers everything.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: frona-data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10GiDeployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: frona
labels:
app: frona
spec:
replicas: 1
selector:
matchLabels:
app: frona
template:
metadata:
labels:
app: frona
spec:
containers:
- name: frona
image: ghcr.io/fronalabs/frona:latest
ports:
- containerPort: 3001
env:
- name: FRONA_BROWSER_WS_URL
value: "ws://localhost:3333"
- name: FRONA_SEARCH_SEARXNG_BASE_URL
value: "http://localhost:8080"
volumeMounts:
- name: data
mountPath: /app/data
readinessProbe:
httpGet:
path: /healthz
port: 3001
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /healthz
port: 3001
initialDelaySeconds: 10
periodSeconds: 30
- name: browserless
image: ghcr.io/browserless/chromium:latest
env:
- name: MAX_CONCURRENT_SESSIONS
value: "10"
- name: PREBOOT_CHROME
value: "true"
volumeMounts:
- name: data
mountPath: /profiles
subPath: browser-profiles
- name: searxng
image: searxng/searxng:latest
env:
- name: SEARXNG_BASE_URL
value: "http://localhost:8080"
volumeMounts:
- name: searxng-config
mountPath: /etc/searxng/settings.yml
subPath: settings.yml
readOnly: true
volumes:
- name: data
persistentVolumeClaim:
claimName: frona-data
- name: searxng-config
configMap:
name: searxng-configFrona uses an embedded database, so only one replica is supported. Do not scale beyond 1.
Service
apiVersion: v1
kind: Service
metadata:
name: frona
spec:
selector:
app: frona
ports:
- name: http
port: 3001
targetPort: 3001
type: ClusterIPSearXNG config
The JSON format is required for the web_search tool to work.
apiVersion: v1
kind: ConfigMap
metadata:
name: searxng-config
data:
settings.yml: |
use_default_settings: true
server:
secret_key: "change-me-to-something-random"
search:
formats:
- html
- jsonServiceMonitor (optional)
If you use Prometheus Operator, add a ServiceMonitor to scrape metrics:
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: frona
labels:
app: frona
spec:
selector:
matchLabels:
app: frona
endpoints:
- port: http
path: /metrics
interval: 30sIngress
The Service is ClusterIP by default. To expose Frona externally, add an Ingress:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: frona
namespace: frona
annotations:
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-buffering: "off"
spec:
ingressClassName: nginx
tls:
- hosts:
- frona.example.com
secretName: frona-tls
rules:
- host: frona.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: frona
port:
number: 3001The timeout and buffering annotations are important. Frona uses Server-Sent Events for real-time streaming, which requires long-lived connections and no response buffering.
Updating
kubectl -n frona rollout restart deployment fronaNotes
- Only one replica is supported. Frona uses an embedded database that doesn't support concurrent access.
- Browserless and SearXNG are internal to the pod. Don't expose them outside the cluster.
- The Frona container runs as a rootless, non-root user (UID 1000).
- For Twilio voice callbacks, set
FRONA_VOICE_CALLBACK_BASE_URLto your public Ingress URL.