Skip to main content

Kubernetes Deployment

SolidPing can be deployed on Kubernetes for high availability and scalability.

Prerequisites​

  • Kubernetes cluster (1.20+)
  • kubectl configured
  • PostgreSQL database (managed or self-hosted)

Basic Deployment​

Namespace and Secret​

# namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: solidping
---
# secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: solidping-secrets
namespace: solidping
type: Opaque
stringData:
db-url: "postgresql://user:password@postgres-host:5432/solidping?sslmode=require"

Deployment​

# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: solidping
namespace: solidping
labels:
app: solidping
spec:
replicas: 2
selector:
matchLabels:
app: solidping
template:
metadata:
labels:
app: solidping
spec:
containers:
- name: solidping
image: ghcr.io/fclairamb/solidping:latest
ports:
- containerPort: 4000
env:
- name: SP_DB_TYPE
value: "postgres"
- name: SP_DB_URL
valueFrom:
secretKeyRef:
name: solidping-secrets
key: db-url
- name: SP_SERVER_LISTEN
value: ":4000"
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /api/mgmt/health
port: 4000
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
httpGet:
path: /api/mgmt/health
port: 4000
initialDelaySeconds: 5
periodSeconds: 10

Service​

# service.yaml
apiVersion: v1
kind: Service
metadata:
name: solidping
namespace: solidping
spec:
selector:
app: solidping
ports:
- protocol: TCP
port: 80
targetPort: 4000
type: ClusterIP

Ingress​

# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: solidping
namespace: solidping
annotations:
kubernetes.io/ingress.class: nginx
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
tls:
- hosts:
- monitoring.example.com
secretName: solidping-tls
rules:
- host: monitoring.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: solidping
port:
number: 80

Apply Configuration​

kubectl apply -f namespace.yaml
kubectl apply -f secret.yaml
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
kubectl apply -f ingress.yaml

SolidPing gzip-compresses its own responses (SP_SERVER_COMPRESSION, default true), so the ingress needs no compression annotation of its own — and turning one on anyway is harmless, since a response that already carries Content-Encoding passes through untouched.

File Storage​

SolidPing writes a handful of blobs outside the database — org logos, status-page assets, incident screenshots. By default (SP_FILESTORAGE_TYPE=local) they land at SP_FILESTORAGE_LOCAL_ROOT (default ./data/files), which is relative to the container's working directory. Unless that path is backed by a mounted volume, every upload is destroyed the next time the pod is recreated — a rollout, a reschedule, anything — and it fails silently: nothing errors at write time, only a later read. See File Storage for the full guide.

Option A — PersistentVolumeClaim (single replica)​

Add a PVC and mount it at an absolute path, so SP_FILESTORAGE_LOCAL_ROOT has no working-directory ambiguity:

# solidping-files-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: solidping-files
namespace: solidping
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 5Gi
spec:
containers:
- name: solidping
image: ghcr.io/fclairamb/solidping:latest
env:
- name: SP_FILESTORAGE_LOCAL_ROOT
value: "/data/files"
volumeMounts:
- name: files
mountPath: /data/files
volumes:
- name: files
persistentVolumeClaim:
claimName: solidping-files
ReadWriteOnce caps you at one replica

A ReadWriteOnce PVC can only be mounted by pods scheduled on a single node, so a Deployment using it cannot scale past replicas: 1 — the sample Deployment above runs replicas: 2, which this option is incompatible with. Drop to a single replica, or use the S3 backend below if you need more than one.

Option B — S3 backend (multi-replica)​

The S3 backend keeps no local state, so it works at any replica count:

env:
- name: SP_FILESTORAGE_TYPE
value: "s3"
- name: SP_FILESTORAGE_S3_BUCKET
value: "solidping"
- name: SP_FILESTORAGE_S3_REGION
value: "us-east-1"
- name: SP_FILESTORAGE_S3_ACCESS_KEY
valueFrom:
secretKeyRef:
name: solidping-secrets
key: s3-access-key
- name: SP_FILESTORAGE_S3_SECRET_KEY
valueFrom:
secretKeyRef:
name: solidping-secrets
key: s3-secret-key

See File Storage for the endpoint/path-style settings a non-AWS S3-compatible store needs, and the full variable reference.

With Helm (Coming Soon)​

A Helm chart for SolidPing is planned for future releases.

PostgreSQL on Kubernetes​

If you need PostgreSQL on the same cluster:

# postgres.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
namespace: solidping
spec:
serviceName: postgres
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:16-alpine
ports:
- containerPort: 5432
env:
- name: POSTGRES_USER
value: solidping
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: solidping-secrets
key: postgres-password
- name: POSTGRES_DB
value: solidping
volumeMounts:
- name: postgres-data
mountPath: /var/lib/postgresql/data
volumeClaimTemplates:
- metadata:
name: postgres-data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi
---
apiVersion: v1
kind: Service
metadata:
name: postgres
namespace: solidping
spec:
selector:
app: postgres
ports:
- port: 5432
clusterIP: None

Distributed Workers​

To deploy workers in different regions, create separate deployments with region-specific configuration:

# worker-us-east.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: solidping-worker-us-east
namespace: solidping
spec:
replicas: 1
selector:
matchLabels:
app: solidping-worker
region: us-east
template:
metadata:
labels:
app: solidping-worker
region: us-east
spec:
containers:
- name: solidping
image: ghcr.io/fclairamb/solidping:latest
env:
- name: SP_REGION
value: "us-east-1"
# ... other environment variables

Browser checks (headless Chrome sidecar)​

The SolidPing image is distroless and ships no browser. To run browser checks, add a headless Chrome sidecar to each checks-worker Deployment and talk to it over localhost:

spec:
containers:
- name: solidping
image: ghcr.io/fclairamb/solidping:latest
env:
- name: SP_CHECKERS_BROWSER_CDP_URL
value: "ws://127.0.0.1:9222"
- name: browser
image: chromedp/headless-shell:151.0.7922.109
args:
- --remote-debugging-address=0.0.0.0
- --remote-debugging-port=9222
# Required from Chrome 111 on for a non-browser websocket client.
- --remote-allow-origins=*
- --disable-gpu
- --no-sandbox
ports:
- containerPort: 9222
volumeMounts:
# Chrome mounts /dev/shm; the container default is far too small.
- name: dshm
mountPath: /dev/shm
resources:
requests: { memory: "512Mi" }
limits: { memory: "2Gi" }
volumes:
- name: dshm
emptyDir:
medium: Memory
sizeLimit: 1Gi

A sidecar rather than a shared browser Deployment: the CDP endpoint stays on localhost (never exposed as a Service — a reachable CDP endpoint is remote control of a browser), and the browser's lifecycle is coupled to the worker that uses it. Pin the image tag so every region runs the same Chrome version.

Each worker caps itself at 4 concurrent browser executions, so size the sidecar for four tabs. Workers that can reach their sidecar advertise a browser capability per region; if the sidecar dies, checks report an error (not a "down") and the region stops advertising the capability on the next heartbeat.

Monitoring​

SolidPing exposes metrics that can be scraped by Prometheus:

# servicemonitor.yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: solidping
namespace: solidping
spec:
selector:
matchLabels:
app: solidping
endpoints:
- port: http
path: /metrics

Next Steps​