Helm Charts

ChartPurposeInstall Command
itopsCore platform (API + UI + Landing + PostgreSQL)helm install itops itops/itops
itops-agentK8s operator (per cluster)helm install itops-agent itops/itops-agent
sla-portalStandalone SLA status pagehelm install sla-portal itops/sla-portal

Installation Values

The platform is fully configurable via Helm values. No hardcoded domains or credentials in the images.

Minimal Production Install

# my-values.yaml
ingress:
  hosts:
    - host: api.yourdomain.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - hosts:
        - api.yourdomain.com
      secretName: itops-api-tls

uiIngress:
  hosts:
    - host: app.yourdomain.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - hosts:
        - app.yourdomain.com
      secretName: itops-ui-tls

ui:
  apiUrl: "https://api.yourdomain.com"
  wsHost: "api.yourdomain.com"

env:
  ITOPS_SERVER_ENVIRONMENT: "production"
  ITOPS_SECURITY_CORS_ALLOWED_ORIGINS: "https://app.yourdomain.com"

secretEnv:
  ITOPS_DATABASE_PASSWORD: "strong-random-password"
  ITOPS_JWT_SECRET: "random-jwt-secret-min-32-chars"
  ITOPS_SECURITY_OPERATOR_API_KEY: "random-api-key-for-agents"
  ITOPS_LICENSE_KEY: "eyJhbGciOiJFZERTQSIs..."  # JWT license token
License key: The license JWT token goes into secretEnv.ITOPS_LICENSE_KEY. Stored as a Kubernetes Secret and injected as an environment variable. Without a valid license, paid plugins (Ticketing, SLA) are hidden. The token is Ed25519-signed (not HMAC) and validated at every startup.
For GitOps: don't commit plaintext secrets. Values under secretEnv: end up in your Git repo. For production use SealedSecret, External Secrets Operator, or sops-age to encrypt these values before committing. The Helm chart reads env vars at runtime — swap the Helm secretEnv for extraEnvFrom: [secretRef: { name: my-vault-synced-secret }] and you never have to touch raw values in Git.
CORS — change before production: ITOPS_SECURITY_CORS_ALLOWED_ORIGINS must be set to your UI hostname (e.g. https://app.yourdomain.com). Leaving the dev default (localhost) in production means browsers outside localhost won't be able to call the API.
Bundled PostgreSQL note: The main chart bundles Bitnami PostgreSQL via the bitnamilegacy/* image mirror and sets global.security.allowInsecureImages: true (needed because Bitnami moved their free public images in Aug 2025). This is safe for self-hosted deployments pulling from Docker Hub, but for production prefer a managed PostgreSQL (RDS, Cloud SQL, self-managed cluster) — set postgresql.enabled: false and point ITOPS_DATABASE_HOST at it.

Security Hardening (shipped since v4.1.2)

The platform ships with defense-in-depth against a compromised admin:

What the install looks like with hardening on (GitOps)

The hardened defaults are shipped in the chart — no opt-in needed. A minimal GitOps values file for a production deployment looks like this:

# platform/itops-values.yaml
ingress:
  hosts:
    - host: api.yourdomain.com
      paths: [{ path: /, pathType: Prefix }]

uiIngress:
  hosts:
    - host: app.yourdomain.com
      paths: [{ path: /, pathType: Prefix }]

ui:
  apiUrl: "https://api.yourdomain.com"
  wsHost: "api.yourdomain.com"

env:
  ITOPS_SERVER_ENVIRONMENT: "production"
  ITOPS_SECURITY_CORS_ALLOWED_ORIGINS: "https://app.yourdomain.com"

  # --- SLA Portal integration (daily report push) ---
  # Public URL goes through the internet lane — already allowed.
  # In-cluster URL needs allowedEgressNamespaces below.
  ITOPS_SLA_PORTAL_URL: "https://sla.yourdomain.com"

# Secrets — in real GitOps, SealedSecret or External Secrets populates these.
# Shown here as plain secretEnv for brevity.
secretEnv:
  ITOPS_DATABASE_PASSWORD: "strong-random"
  ITOPS_JWT_SECRET: "32+chars"
  ITOPS_SECURITY_OPERATOR_API_KEY: "shared-agent-key"
  ITOPS_LICENSE_KEY: "eyJhbGciOiJFZERTQSIs..."
  ITOPS_SLA_PORTAL_API_KEY: "sla-portal-shared-key"

networkPolicy:
  enabled: true
  # Leave default unless your SLA Portal lives in a different namespace than "sla-portal"
  allowedEgressNamespaces:
    - sla-portal

Opening specific targets when the default is too strict

When a webhook needs to reach an in-cluster service (private n8n, internal Jira, managed DB on RFC1918), use these explicit escape hatches — never blanket-allow everything:

env:
  # Restrict webhooks to a strict host allowlist (recommended if you have
  # ANY in-cluster webhook target)
  ITOPS_SECURITY_WEBHOOK_HOST_ALLOWLIST: "n8n.corp.local,hooks.slack.com,api.pagerduty.com"

  # Dev-only nuclear option: disable the private-IP block entirely.
  # Don't set this in production — it reopens the SSRF hole.
  # ITOPS_SECURITY_ALLOW_PRIVATE_WEBHOOKS: "true"

networkPolicy:
  extraDatabaseCIDRs:
    - 10.20.30.0/24   # managed PostgreSQL subnet
  allowedEgressNamespaces:
    - sla-portal      # keep for SLA Portal daily push
    - automation      # e.g. an in-cluster n8n namespace

What breaks if you ignore the defaults

helm install itops itops/itops -n itops --create-namespace -f my-values.yaml

Default login: admin / Password123! (change after first login)

Istio Support

If your cluster uses Istio instead of nginx Ingress:

istio:
  enabled: true
  tls:
    enabled: true
    credentialName: "my-tls-cert"

ingress:
  enabled: false   # disable nginx Ingress
uiIngress:
  enabled: false

External Database

To use an existing PostgreSQL instead of the bundled one:

postgresql:
  enabled: false

env:
  ITOPS_DATABASE_HOST: "my-postgres.default"
  ITOPS_DATABASE_PORT: "5432"
  ITOPS_DATABASE_NAME: "itops"
  ITOPS_DATABASE_USER: "itops"

secretEnv:
  ITOPS_DATABASE_PASSWORD: "my-db-password"

Plugin Management

Plugins (Ticketing, SLA) can be enabled/disabled from the admin UI at /admin/plugins. Disabling a plugin hides its menus and stops background processing, but preserves all existing data.