Kubernetes Deployment
Sparrow ships a Helm chart at charts/sparrow/ for Kubernetes deployments.
Prerequisites: Helm 3, kubectl, and a running Kubernetes cluster.
What You Get
Section titled “What You Get”With default values, helm install creates:
- A Deployment running 1 Sparrow pod (stateless, safe to scale)
- A ClusterIP Service exposing HTTP (
:8080) and gRPC (:50051) - A ConfigMap with application settings
- A Secret with
DATABASE_URLandSPARROW_ENCRYPTION_KEY - Liveness (
/health) and readiness (/ready) probes - Hardened security context (nonroot, read-only fs, all capabilities dropped)
Optional resources created when enabled: Ingress, HPA, PodDisruptionBudget, ServiceAccount, and a bundled PostgreSQL StatefulSet.
Install with External Database
Section titled “Install with External Database”The expected production setup. You provide your own PostgreSQL:
helm install sparrow charts/sparrow/ \ --set secrets.databaseURL="postgres://user:pass@your-db:5432/sparrow?sslmode=require"Sparrow runs migrations on startup automatically — no separate migration step needed.
Install with Bundled PostgreSQL
Section titled “Install with Bundled PostgreSQL”For evaluation and development. Deploys a single-replica PostgreSQL StatefulSet with a 10Gi PVC:
helm install sparrow charts/sparrow/ \ --set postgresql.enabled=trueWhen postgresql.enabled=true and secrets.databaseURL is empty, the connection string is auto-constructed from postgresql.auth.* values.
Local End-to-End with k3d
Section titled “Local End-to-End with k3d”A complete walkthrough: create a local cluster, install Sparrow with bundled PostgreSQL, and verify the full event-to-delivery flow.
1. Create cluster
Section titled “1. Create cluster”# Install k3d if needed: https://k3d.iok3d cluster create sparrow-dev -p "8080:80@loadbalancer"This maps localhost:8080 to the k3d Traefik ingress on port 80.
2. Build and load the image
Section titled “2. Build and load the image”make docker-buildk3d image import ghcr.io/sarathsp06/sparrow:latest -c sparrow-dev3. Install the chart
Section titled “3. Install the chart”helm install sparrow charts/sparrow/ \ --set postgresql.enabled=true \ --set image.repository=ghcr.io/sarathsp06/sparrow \ --set image.tag=latest \ --set image.pullPolicy=Never \ --set ingress.enabled=true \ --set ingress.hosts[0].host="" \ --set ingress.hosts[0].paths[0].path=/ \ --set ingress.hosts[0].paths[0].pathType=PrefixSetting ingress.hosts[0].host="" creates an Ingress that matches all hostnames, so localhost:8080 routes through Traefik to the Sparrow service.
4. Wait for pods
Section titled “4. Wait for pods”kubectl get pods -wWait until the sparrow pod is Running and 1/1 ready. The PostgreSQL StatefulSet starts first, then Sparrow’s init container waits for it before the main container starts.
5. Verify
Section titled “5. Verify”# Health checkcurl -s http://localhost:8080/health | jq .
# Register an event typecurl -s -X POST http://localhost:8080/webhook.EventService/RegisterEvent \ -H "Content-Type: application/json" \ -d '{"name":"order.created","description":"New order","active":true}'
# Register a webhookcurl -s -X POST http://localhost:8080/webhook.WebhookService/RegisterWebhook \ -H "Content-Type: application/json" \ -d '{"namespace":"default","url":"https://testhooks.sarathsadasivan.com/hooks","events":["order.created"],"active":true}'
# Push an eventcurl -s -X POST http://localhost:8080/webhook.EventService/PushEvent \ -H "Content-Type: application/json" \ -d '{"namespace":"default","event":"order.created","payload":{"order_id":"ord_k8s_001","amount":42.00}}'
# Check deliveriescurl -s -X POST http://localhost:8080/webhook.DeliveryService/ListDeliveries \ -H "Content-Type: application/json" -d '{}' | jq '.deliveries[0].status'6. Open the web UI
Section titled “6. Open the web UI”The embedded dashboard is served at http://localhost:8080/ by default (sparrow.serveUI: "true").
7. Cleanup
Section titled “7. Cleanup”helm uninstall sparrowk3d cluster delete sparrow-devConfiguration
Section titled “Configuration”All chart values are documented with comments in values.yaml. Key areas:
image.* — Container image, tag, pull policy.
secrets.databaseURL — PostgreSQL connection string. Required when postgresql.enabled=false. When using an existing Kubernetes Secret, set secrets.existingSecret instead — it must contain DATABASE_URL and SPARROW_ENCRYPTION_KEY keys.
secrets.encryptionKey — 64-char hex key for envelope encryption of webhook secrets and headers. Generate with openssl rand -hex 32. Required — the server will not start without it.
sparrow.extraEnv — Inject arbitrary environment variables. Use this for OpenTelemetry, feature flags, or any env var Sparrow supports:
sparrow: extraEnv: - name: OTEL_EXPORTER_OTLP_ENDPOINT value: "http://otel-collector.observability:4318"postgresql.* — Bundled PostgreSQL. Off by default. Enable with postgresql.enabled: true.
ingress.* — Ingress resource. Off by default. Set ingress.enabled: true and configure hosts and optionally tls.
autoscaling.* — HPA. Off by default. Set autoscaling.enabled: true. A PodDisruptionBudget is auto-created when multiple replicas are running.
resources.* — CPU/memory requests and limits for the Sparrow container. Defaults: 100m/128Mi requests, 500m/512Mi limits.
Using a Values File
Section titled “Using a Values File”For anything beyond a quick test, use a values file:
image: tag: "1.0.0"
secrets: existingSecret: sparrow-prod-creds
ingress: enabled: true className: nginx hosts: - host: sparrow.example.com paths: - path: / pathType: Prefix tls: - secretName: sparrow-tls hosts: - sparrow.example.com
autoscaling: enabled: true minReplicas: 3 maxReplicas: 20
resources: requests: cpu: 250m memory: 256Mi limits: cpu: "1" memory: 1Gi
sparrow: extraEnv: - name: OTEL_EXPORTER_OTLP_ENDPOINT value: "http://otel-collector.observability:4318"helm install sparrow charts/sparrow/ -f values-prod.yamlSecurity
Section titled “Security”The chart applies a hardened security context by default:
- Sparrow runs as nonroot (UID 65532, matching the distroless image), read-only root filesystem, all capabilities dropped,
RuntimeDefaultseccomp profile. - Bundled PostgreSQL runs as UID 999 (the
postgresuser in the alpine image) withRuntimeDefaultseccomp. - Init containers are resource-limited and run with the same restrictions.
Health and Disruption Budget
Section titled “Health and Disruption Budget”Probes are configured automatically. Liveness hits /health (checks DB + queue, returns 503 on failure). Readiness hits /ready (always 200).
A PodDisruptionBudget (maxUnavailable: 1) is created automatically when replicaCount > 1 or autoscaling.enabled=true.
Observability
Section titled “Observability”OTel export is off by default. Set OTEL_EXPORTER_OTLP_ENDPOINT via sparrow.extraEnv to enable traces, metrics, and logs export. The chart does not deploy an OTel Collector — point the endpoint at your existing collector.
Makefile Targets
Section titled “Makefile Targets”make docker-build # Build Docker image locallymake docker-push # Push to ghcr.iomake helm-lint # Lint the chartmake helm-template # Dry-run render (external DB)make helm-template-pg # Dry-run render (bundled PG)make helm-package # Package into build/sparrow-*.tgzReleases
Section titled “Releases”On every git tag (v*), the release workflow builds cross-platform binaries, packages the Helm chart, pushes a multi-arch Docker image to ghcr.io/sarathsp06/sparrow, and creates a GitHub Release with all artifacts.