Skip to content

Quick Start ​

This walkthrough brings up a complete platform — datastores, a LiteLLM Gateway, Langfuse observability, and the LibreChat chat app — on a cluster that already has the operator installed. It runs without a license (the Community edition); with one, the chat app and tracing wire themselves.

Everything can be applied at once: each resource becomes Ready once the ones it references are, and the operator requeues while it waits.

Use the bundled working set

The repository ships exactly these manifests under config/samples/quickstart/. Replace every REPLACE_ME in secrets.yaml, then:

bash
kubectl apply -k config/samples/quickstart/

config/samples/*.yaml holds one reference example per resource kind with more options (Vault, Wäg, guardrails, Stacks, …); those are examples to copy from, not a set to apply as a whole.

0. Namespaces ​

yaml
apiVersion: v1
kind: Namespace
metadata: { name: forge-data }
---
apiVersion: v1
kind: Namespace
metadata: { name: forge-gateway }
---
apiVersion: v1
kind: Namespace
metadata: { name: forge-langfuse }
---
apiVersion: v1
kind: Namespace
metadata: { name: forge-ui }

1. (Optional) Apply a license ​

Without a license the platform runs as the Community edition (one instance per type, manual wiring). To enable multi-instance and auto-wiring, download your license (or start a trial) from the customer portal at portal.navique.dev, then create the license Secret and a License:

bash
kubectl -n navique-system create secret generic navique-license \
  --from-file=license=./license.lic
yaml
apiVersion: core.navique.com/v1alpha1
kind: License
metadata:
  name: cluster            # singleton — must be named "cluster"
spec:
  secretRef:
    name: navique-license
    key: license
    namespace: navique-system

See Managing a License.

2. Credentials ​

The workloads read plain Kubernetes Secrets — nothing else to install. Replace every REPLACE_ME:

yaml
# Plain Kubernetes Secrets: nothing else to install. Replace every REPLACE_ME.
# In production, have a SecretsManagement (Vault, Azure Key Vault, …) produce
# these instead and set the workloads' secretsRef.

# The model provider key(s) the gateway uses (models[].refSecretKey).
apiVersion: v1
kind: Secret
metadata: { name: model-credentials, namespace: forge-gateway }
stringData:
  OPENAI_API_KEY: REPLACE_ME
---
# Credentials for Langfuse's object storage. object-storage.yaml starts a small
# MinIO with these; with your own S3, put its access keys here.
apiVersion: v1
kind: Secret
metadata: { name: langfuse-s3, namespace: forge-langfuse }
stringData:
  access-key-id: forge-langfuse
  secret-access-key: REPLACE_ME_min_8_chars
---
# Only used WITHOUT a licence: the key the chat app sends to the gateway.
# Create a key in the gateway's LiteLLM admin UI and paste it here. With the
# auto-wiring licence the operator mints and injects a key itself and ignores
# this Secret.
apiVersion: v1
kind: Secret
metadata: { name: chat-gateway-key, namespace: forge-ui }
stringData:
  LITELLM_API_KEY: REPLACE_ME

Credentials from a vault

In production, let a SecretsManagement produce these Secrets from HashiCorp Vault, Azure Key Vault or any External Secrets provider, and set the workloads' optional secretsRef to it.

3. Object storage for Langfuse ​

Langfuse v3 needs object storage. For evaluation, a single-replica MinIO:

yaml
# EVALUATION ONLY: a single-replica MinIO (no redundancy, ephemeral storage) so
# Langfuse v3 has the object storage it requires. In production, use your own
# S3 / Azure Blob / GCS and drop this file (see observability.yaml).
apiVersion: apps/v1
kind: Deployment
metadata: { name: minio, namespace: forge-langfuse }
spec:
  replicas: 1
  selector: { matchLabels: { app: minio } }
  template:
    metadata: { labels: { app: minio } }
    spec:
      containers:
        - name: minio
          image: quay.io/minio/minio:latest
          args: ["server", "/data"]
          env:
            - { name: MINIO_ROOT_USER, valueFrom: { secretKeyRef: { name: langfuse-s3, key: access-key-id } } }
            - { name: MINIO_ROOT_PASSWORD, valueFrom: { secretKeyRef: { name: langfuse-s3, key: secret-access-key } } }
          ports: [ { containerPort: 9000 } ]
          volumeMounts: [ { name: data, mountPath: /data } ]
      volumes: [ { name: data, emptyDir: {} } ]
---
apiVersion: v1
kind: Service
metadata: { name: minio, namespace: forge-langfuse }
spec:
  selector: { app: minio }
  ports: [ { port: 9000, targetPort: 9000 } ]
---
# Creates the bucket Langfuse writes to.
apiVersion: batch/v1
kind: Job
metadata: { name: minio-bucket, namespace: forge-langfuse }
spec:
  backoffLimit: 20
  template:
    spec:
      restartPolicy: OnFailure
      containers:
        - name: mc
          image: quay.io/minio/mc:latest
          env:
            - { name: AK, valueFrom: { secretKeyRef: { name: langfuse-s3, key: access-key-id } } }
            - { name: SK, valueFrom: { secretKeyRef: { name: langfuse-s3, key: secret-access-key } } }
          command: ["sh", "-c", "mc alias set local http://minio.forge-langfuse.svc:9000 \"$AK\" \"$SK\" && mc mb --ignore-existing local/langfuse"]

In production, drop this and point blob (step 6) at your own S3, Azure Blob Storage or Google Cloud Storage — see Observability.

4. Datastores ​

yaml
# The shared datastores, sized for evaluation (one replica each). Each consumer
# gets its own logical database. Without the auto-wiring licence the databases
# must be declared here; with it, a datastore also creates the databases its
# referencing workloads ask for.
apiVersion: core.navique.com/v1alpha1
kind: PostgresCluster
metadata: { name: forge-pg, namespace: forge-data }
spec:
  type: cnpg
  mode: managed
  managed: { instances: 1, storageSize: 5Gi }
  databases:
    - { name: litellm }
    - { name: langfuse }
---
apiVersion: core.navique.com/v1alpha1
kind: ClickHouseCluster
metadata: { name: forge-ch, namespace: forge-data }
spec:
  type: clickhouse
  mode: managed
  databases:
    - { name: langfuse }
---
apiVersion: core.navique.com/v1alpha1
kind: RedisInstance
metadata: { name: forge-redis, namespace: forge-data }
spec:
  type: ot-container-kit
  mode: managed
  managed: { topology: standalone, storageSize: 1Gi }
---
# The chat app's conversation store and search index live next to it.
apiVersion: core.navique.com/v1alpha1
kind: MongoCluster
metadata: { name: forge-mongo, namespace: forge-ui }
spec:
  type: mck
  mode: managed
  managed: { members: 1, version: "8.0.4", storageSize: 5Gi }
  databases:
    - { name: LibreChat }
---
apiVersion: core.navique.com/v1alpha1
kind: MeilisearchInstance
metadata: { name: forge-meili, namespace: forge-ui }
spec:
  type: meilisearch
  mode: managed
  managed: { storageSize: 1Gi }

Don't have block storage or want to bring your own?

Set mode: external and supply a connectionSecretRef, or mode: adopt to point at a datastore you already run. See each resource's reference page.

5. The Gateway ​

yaml
# The LiteLLM gateway. Model keys come from the model-credentials Secret.
apiVersion: core.navique.com/v1alpha1
kind: Gateway
metadata: { name: gateway, namespace: forge-gateway }
spec:
  database:
    mode: postgresCluster
    postgresClusterRef: { name: forge-pg, namespace: forge-data }
    databaseName: litellm
  instance:
    replicas: 1
    masterKey: { autoGenerate: true }
    saltKey: { autoGenerate: true }
  # Traces to Langfuse — wired automatically with the auto-wiring licence.
  observabilityRef: { name: observability, namespace: forge-langfuse }
  models:
    - name: gpt-4o-mini
      modelName: gpt-4o-mini
      model: openai/gpt-4o-mini
      refSecretKey: OPENAI_API_KEY

6. Observability (Langfuse) ​

yaml
# Langfuse v3 on the shared datastores. The first person to sign up becomes its
# administrator (set langfuse.bootstrap to seed an admin, org and API keys).
apiVersion: core.navique.com/v1alpha1
kind: Observability
metadata: { name: observability, namespace: forge-langfuse }
spec:
  type: langfuse
  langfuse:
    postgres: { mode: ref, ref: { name: forge-pg, namespace: forge-data }, databaseName: langfuse }
    clickhouse: { mode: ref, ref: { name: forge-ch, namespace: forge-data }, databaseName: langfuse }
    redis: { mode: ref, ref: { name: forge-redis, namespace: forge-data } }
    blob:
      provider: s3
      s3:
        bucket: langfuse
        region: us-east-1
        endpoint: http://minio.forge-langfuse.svc:9000
        forcePathStyle: true
        credentialsSecretRef: { name: langfuse-s3 }

7. The chat app (LibreChat) ​

yaml
# LibreChat. With the auto-wiring licence, gatewayRef is enough: the operator
# mints a gateway key and fills the model list. Without it, the chat app uses the
# gateway address and key below (secrets.yaml: chat-gateway-key).
apiVersion: core.navique.com/v1alpha1
kind: ChatUI
metadata: { name: forge-ui, namespace: forge-ui }
spec:
  gatewayRef: { name: gateway, namespace: forge-gateway }
  gateway:
    url: http://gateway.forge-gateway.svc.cluster.local:4000/v1
    apiKeySecretRef: { name: chat-gateway-key, key: LITELLM_API_KEY }
  models: [ gpt-4o-mini ]
  mongo: { mode: ref, ref: { name: forge-mongo }, databaseName: LibreChat }
  meilisearch: { mode: ref, ref: { name: forge-meili } }

With a license and gatewayRef, the operator mints a gateway key for the chat app and injects the gateway address itself. Without one, the chat app uses gateway.{url, apiKeySecretRef} — see Auto-Wiring.

8. Watch it converge ​

bash
# Datastores
kubectl -n forge-data get postgrescluster,clickhousecluster.core.navique.com,redisinstance
kubectl -n forge-ui   get mongocluster,meilisearchinstance

# Workloads
kubectl -n forge-gateway  get gateway
kubectl -n forge-langfuse get observability
kubectl -n forge-ui       get chatui

# Drill into a resource's conditions if something is pending
kubectl -n forge-gateway describe gateway gateway

Each resource reports Ready once its dependencies are up; its conditions say exactly what it is waiting on.

9. Reach the platform ​

Nothing is exposed outside the cluster in this set; forward a port to try it:

bash
kubectl -n forge-ui port-forward svc/forge-ui-librechat 3080:3080         # chat app → http://localhost:3080
kubectl -n forge-langfuse port-forward svc/observability-web 3000:3000   # Langfuse → http://localhost:3000

Pick the model in the chat app and chat — requests flow through the gateway, and (with a license) traces appear in Langfuse. To publish them, set ingress on the resources — see each resource's reference page.

Clean up ​

Deleting a resource triggers its finalizer: emitted upstream CRs and owned Helm releases are removed, and any capability operator the operator installed is uninstalled once nothing else needs it. Adopted and external resources are never touched. See Provenance & Lifecycle.

bash
kubectl delete -k config/samples/quickstart/

Open core under AGPL-3.0. Enterprise components are proprietary and license-gated.