Qyra

Production deployment checklist

What to configure for an enterprise-grade self-hosted deployment with the official Helm chart

This is the checklist for running a production-grade, self-hosted Qyra deployment with the official Helm chart (qyra/helm-charts). The guide is intended for platform/devops engineers deploying their own Qyra instance. The guide gives you 3-tiers of deployments with increasing configuration options.

Tier
Tier 1Recommended for evaluating Qyra or a PoC
Tier 2A best practice scalable production deployment
Tier 3Optional features and observability

Tier 1: Evaluation Deployment

Follow the self-hosting guide (or docker compose for a local spin-up) for a minimum production setup suitable for evaluating Qyra.

Prerequisites

Checklist

Tier 2: Scalable deployment

None of these steps are required but are recommended for running Qyra beyond a PoC.

Operations

Workers and scaling

Infrastructure dependencies

Security and authentication

Tier 3: Optional features and observability

Enterprise features

Integrations (enable what you use)

Observability

Architecture: what you're deploying

ComponentChart valueWhat it does
Backend(always on)API + UI. Scale horizontally, 2+ replicas
Scheduler workerscheduler.enabledScheduled deliveries, Slack/email sends, exports, syncs
NATSnats.enabledJetStream message bus for async query execution
Warehouse NATS workerwarehouseNatsWorker.enabledExecutes warehouse queries + streams results to S3
Pre-aggregate NATS workerpreAggregateNatsWorker.enabledBuilds pre-aggregated materializations (Enterprise)
Headless browserbrowserless-chrome.enabledChromium pool for screenshots/PDFs
Migration jobmigrationJob.enabledPre-upgrade Helm hook that runs database migrations exactly once
PostgreSQLexternal (postgresql.enabled: false)Application state. Don't use the bundled subchart, even for a PoC
S3 bucket(s)externalQuery results, downloads, pre-agg materializations, data apps

The chart wires up environment variables for you in three buckets:

  • configMap.* — non-sensitive env vars, applied to backend and all workers
  • secrets.* — sensitive env vars, rendered into a Kubernetes Secret (or bring your own via existingSecret)
  • extraEnv / schedulerExtraEnv — raw env entries, including valueFrom.secretKeyRef

The full list of supported environment variables lives in the environment variables reference.

Core configuration

The chart's essential first-boot values — SITE_URL and QYRA_SECRET are the two to get right before you start:

image:
  repository: quanvio/qyra
  tag: "0.xxxx.x"        # always pin; upgrade deliberately

configMap:
  SITE_URL: https://qyra.yourcompany.com
  SECURE_COOKIES: "true"
  TRUST_PROXY: "true"
  QYRA_MODE: default
  QYRA_MAX_PAYLOAD: "40mb"   # default 5mb is too small for large dbt manifests

existingSecret: qyra-secrets   # QYRA_SECRET, S3 keys, SMTP password, ...
  • SITE_URL signs invite emails, OAuth redirect URIs, Slack unfurls, and delivery links — set the final https:// URL before first boot.
  • QYRA_SECRET signs session cookies and encrypts data at rest in Postgres. Set it and store it durably; losing it means losing access to encrypted data.
  • SECURE_COOKIES and TRUST_PROXY must both be "true" behind a TLS-terminating proxy, and COOKIES_MAX_AGE_HOURS sets session length — see Secure Qyra with HTTPS.
  • Every variable is documented in the environment variables reference.

For secrets, prefer existingSecret populated by External Secrets Operator or a CSI driver so credentials stay out of Helm values and git.

Headless browser

Enabled by default in the chart — keep it on, and tune the browserless timeouts and memory guards for large dashboards. The browser renders dashboards by calling SITE_URL, so it must reach that URL from inside the cluster (use INTERNAL_QYRA_HOST if it can't). See Headless browser for the container and backend variables, and Resource recommendations for sizing.

Upgrades and operations

Pin image.tag, upgrade at least monthly, rehearse each upgrade in a UAT instance that mirrors production, take the database backup before you start, and enable the migration Job for multi-replica deployments. Before each upgrade, set upgrade.mode from the upgrade-safety verdict: RollingUpdate for true, or Recreate for false or unknown. Chart 2.16.284 and later runs the shutdown barrier before the migration Job when the mode is Recreate. Chart 2.16.283 and earlier needs the manual fallback. See the upgrade runbook for both paths and custom deployments. Versioning policy, upgrade cadence, and advisory monitoring are in Upgrading Qyra.

Scheduler worker

Run a dedicated scheduler worker so a heavy dashboard export can't starve the API — see Scheduler worker. For async warehouse queries, see the NATS workers overview and warehouse workers, including the critical rule: never enable nats.enabled without warehouseNatsWorker.enabled.

Sizing and availability

Size per-component resource requests and run 2+ backend replicas with pod anti-affinity and a pod disruption budget — the full component table and availability settings are in Resource recommendations.

PostgreSQL

Run external managed Postgres with high availability, backups, the uuid-ossp and pgvector extensions, and a connection budget — setup and production guidance are in Configure Qyra to use an external database.

Object storage

Use a dedicated bucket per purpose with lifecycle rules, blocked public access, and scoped credentials — setup and the full bucket strategy are in Configure Qyra to use external object storage.

Email deliverability

Env vars are in the SMTP reference. Best practice on top:

  • Use a transactional provider (SES, Postmark, SendGrid) — Qyra Cloud sends through Postmark.
  • Set up SPF/DKIM for the sender domain so scheduled deliveries don't land in spam.

Load balancer and networking

HTTPS end-to-end, a load-balancer timeout ≥ 300s, a health check on GET /api/v1/health, and a request body limit ≥ QYRA_MAX_PAYLOAD — covered in Secure Qyra with HTTPS.

Health probes

Qyra serves three health endpoints, and they are not interchangeable:

ProbeEndpointWhy
Liveness/api/v1/livezAnswers without touching the database, so a database blip does not restart every pod at once
Readiness/api/v1/readyzTTL-cached, gates on schema migration state. Keeps a pod out of the load balancer while it is migrating, without restarting it
Startup/api/v1/livezSame endpoint as liveness, so a slow-starting pod is not killed before it is ready to serve

Avoid /api/v1/health for any of the three. It queries the database on every request, which is fine for a manual curl after a deploy but means a brief database blip fails every pod's check at the same time.

The chart already points startupProbe and livenessProbe at /api/v1/livez by default. Set the readiness path explicitly:

qyraBackend:
  readinessProbe:
    path: /api/v1/readyz

Recent chart versions resolve this path for you on Qyra 1.169.1 and later. Setting it explicitly is always supported, and an explicit value always wins.

Only point a readiness probe at /api/v1/readyz on Qyra 1.169.1 or later. Before that, a parked migration marked every working pod as not ready, so a readiness probe on /api/v1/readyz could pull the whole backend out of service over a single stuck migration. See what shipped when and the upgrade runbook for the full reasoning and the 503 reasons.

Leave worker probes alone. Worker pods serve their own handler at /api/v1/health (in-memory state, no database query) and do not serve /api/v1/readyz at all.

Authentication policy

Enterprise deployments should be SSO-only, with password authentication disabled and account linking enabled — per-provider setup is in use SSO login for self-hosted Qyra. Also set a personal access token policy (PAT_ALLOWED_ORG_ROLES, PAT_MAX_EXPIRATION_TIME_IN_DAYS, or DISABLE_PAT), and keep ALLOW_MULTIPLE_ORGS: "false" (default) for a single-company instance.

Security hardening

  • CSP enforcement: QYRA_CSP_REPORT_ONLY: "false" (default is report-only; enforce in production), plus QYRA_CSP_ALLOWED_DOMAINS for any extra origins you load from.
  • CORS: leave disabled unless embedding; if embedding, QYRA_CORS_ENABLED: "true" with an explicit QYRA_CORS_ALLOWED_DOMAINS list — never *.
  • Egress policy: Qyra needs your warehouse, S3, SMTP, api.keygen.sh (license), your IdP, and any AI provider endpoints — plus roadmap.qyraflow.com if you enable the organization roadmap. RudderStack product telemetry to analytics.qyraflow.com is on by default and can be disabled or redirected. See Data flows and telemetry for the full egress matrix and controls.
  • NetworkPolicies: the chart only ships one for NATS (keep nats.networkPolicy.enabled: true, the default); add your own default-deny + allow rules for backend ↔ postgres/browserless/S3 if your cluster uses them.
  • Pod security: the chart sets no podSecurityContext / securityContext by default — add runAsNonRoot and drop capabilities per your Pod Security Standards baseline.
  • Soft delete for content recovery: SOFT_DELETE_ENABLED: "true" (plus SOFT_DELETE_RETENTION_DAYS, default 30).

Enterprise features

License key setup and validation is covered in enterprise license keys — the key is validated against https://api.keygen.sh on every server start, so allowlist that domain in your egress policy.

Enterprise feature flags

Enable the Enterprise features you use through configMap — each is documented in the environment variables reference:

  • Caching: RESULTS_CACHE_ENABLED, AUTOCOMPLETE_CACHE_ENABLED, CACHE_STALE_TIME_SECONDS.
  • Governance: SERVICE_ACCOUNT_ENABLED, CUSTOM_ROLES_ENABLED.
  • Embedding: EMBEDDING_ENABLED with QYRA_IFRAME_EMBEDDING_DOMAINS.

AI Analyst

Set AI_COPILOT_ENABLED: "true", choose AI_DEFAULT_PROVIDER (openai, azure, anthropic, openrouter, or bedrock) with the matching API key, and AI_EMBEDDING_ENABLED: "true" for verified answers (requires pgvector). See AI agents for provider setup and LLM-gateway routing, and the environment variables reference for guardrails such as AI_COPILOT_MAX_QUERY_LIMIT and AI_COPILOT_ALLOWED_PROJECT_UUID. For the MCP endpoint, see MCP.

Data apps

Serve app previews from a separate domain (APP_RUNTIME_PREVIEW_ORIGIN) so untrusted app content never shares an origin with your Qyra session cookies, and use a persistent apps bucket with no delete lifecycle. Sandbox providers and their security model are in sandboxes; configuration in self-hosting data apps.

Observability

Enable Prometheus metrics and structured JSON logging on every pod, and scrape them from your monitoring stack:

  • Metrics and alerting guidance: Prometheus metrics. Scrape port 9090 on all pods labelled app.kubernetes.io/name=qyra (the chart ships no ServiceMonitor/PodMonitoring — create one; Qyra Cloud scrapes at a 30s interval). If you run NATS, its Prometheus exporter is on port 7777 (nats.promExporter.enabled: true).
  • Log configuration: Configure logging. Ship QYRA_LOG_FORMAT: json to your log platform; QYRA_LOG_LEVEL: audit adds an audit trail of user actions.
  • Alert on HTTP p95/error rate (http_server_request_duration_seconds), queue depth / scheduler job failures, Postgres pool saturation, and event-loop lag.
  • Distributed traces (optional): OpenTelemetry tracing. Set QYRA_OTEL_TRACES_ENABLED: "true" and point OTEL_EXPORTER_OTLP_ENDPOINT at your collector.

Baseline per-component resource requests for a standard self-hosted instance deployed with the official Helm chart:

ComponentCPUMemoryEphemeralReplicas
Backend500m–11.5–4 Gi1–2 Gi2+
Scheduler worker500m1425 Mi1 Gi1
Warehouse NATS worker250m1.5 Gi9 Gi1
Pre-aggregate NATS worker650m4 Gi9 Gi1
Browserless24 Gi1 Gi1
NATS100m256 Mi–1 Gi1

NATS workers buffer large result sets on ephemeral disk before uploading to S3 — the 9 Gi ephemeral-storage request is not a typo.

Availability

Run at least two backend replicas with pod anti-affinity and a pod disruption budget:

replicaCount: 2

autoscaling:
  enabled: true
  minReplicas: 2
  maxReplicas: 10
  targetCPUUtilizationPercentage: 60   # backend only; workers scale via replicas

podAntiAffinity:
  enabled: true        # spreads each component across nodes (hard) and zones (soft)

podDisruptionBudget:
  enabled: true
  minAvailable: 1