Using your own infrastructure
The chart bundles everything the platform needs, so an install on hardware you own works with no external dependencies. On a cloud you probably already run managed equivalents — and would rather the platform used those than keep a database alive inside the cluster.
Every bundled dependency is optional. Each has an enabled flag and a
documented external path, and nothing is duplicated in forked templates: the
two postures differ in values only. The
configurator asks the database, Redis and object
storage choices and writes the values below for you.
| Bundled | Turn it off with | Replace it with |
|---|---|---|
| PostgreSQL (CloudNativePG) | cnpg.enabled: false |
database.source |
| Redis | redis.enabled: false |
config.redis* + secrets.managed.edisyl-redis |
| Object storage (MinIO) | minio.enabled: false |
config.objectStorage* + secrets.managed.edisyl-object-storage |
| Inngest | inngest.enabled: false |
config.inngestBaseUrl + secrets.managed.edisyl-inngest |
Addresses and other settings are values under config.*; only credentials go
in a secrets.managed group. A setting left in a managed Secret fails the
render and names the config.* key it moved to — see
Configuration.
Where the chart can tell that a replacement is missing, it fails the render and
names the value — a Helm error rather than a CrashLoopBackOff twenty minutes
later. That covers the connection URLs, DATABASE_URL, the Redis host, port,
user and password, and the object store’s bucket and URL, but not everything a
server needs: pgvector, the Redis modules and a reachable endpoint surface at
boot instead. Treat the checks as a safety net, not a specification.
PostgreSQL
Section titled “PostgreSQL”database.source is the single switch for where DATABASE_URL comes from.
A connection secret you already have
Section titled “A connection secret you already have”cnpg: enabled: false
database: source: existing-secret existingSecret: name: my-postgres-connection key: uriThe credential never passes through values — the chart reads it from a Secret already in the release namespace. This is the better option where something else provisions your database, because nothing has to copy the password into a values file that ends up in git.
A connection string you supply
Section titled “A connection string you supply”cnpg: enabled: false
database: source: managed
secrets: managed: edisyl-database: data: DATABASE_URL: "postgresql://user:password@host:5432/edisyl"The render fails if that value is blank. Under
secrets.provider: external-secrets the value comes from your store instead —
see the values reference.
Your database still needs pgvector
Section titled “Your database still needs pgvector”Whatever you point at, the extension must be available — the first migration
runs CREATE EXTENSION vector. Amazon RDS and Cloud SQL both offer it, but it
is not enabled by default on either; check before you install.
The auth service keeps its own database
Section titled “The auth service keeps its own database”This is the part that catches people. The auth service does not share the application database, and the chart only provisions its database when the bundled cluster is running.
With cnpg.enabled: false the chart creates nothing. You provision that
database yourself — a database, a role that owns it, and a Secret holding the
connection URI — then point the chart at it:
edisyl: auth: database: secretName: my-auth-db-connection secretKey: uriSetting edisyl.auth.database.create: true while cnpg.enabled is false fails
the render, because there is no bundled cluster to create it in.
So does the LLM gateway
Section titled “So does the LLM gateway”The same rule applies to the
LLM gateway, which runs in every
install. Bifrost owns and migrates its own schema, and the chart never puts it
in the application’s database. With cnpg.enabled: false, provision a database
and a role for it and put the connection in a Secret — discrete keys, not a
URI, because Bifrost has no DSN input:
bifrost: database: source: existing-secret existingSecret: name: bifrost-db # The key names below are the defaults; change them only if your Secret # uses different ones. keys: { host: host, port: port, user: username, password: password, dbName: dbname } sslMode: requirebifrost.database.source defaults to cnpg, so with cnpg.enabled: false a
render that does not set it fails, for the same reason the auth database does.
redis: enabled: false
config: redisHost: redis.example.internal redisPort: "6379" redisUser: default redisTls: "true" # optional
secrets: managed: edisyl-redis: data: REDIS_PASSWORD: "password" edisyl-redis-lattice: data: LATTICE_REDIS_URL: "rediss://default:password@redis.example.internal:6379/1" edisyl-redis-inngest: data: INNGEST_REDIS_URI: "rediss://default:password@redis.example.internal:6379/2"The host, port, user and TLS setting are configuration, under config.redis*;
edisyl-redis holds the password and nothing else. With bundled Redis off, the
render fails if any of config.redisHost, config.redisPort,
config.redisUser, REDIS_PASSWORD or LATTICE_REDIS_URL is unset, and
INNGEST_REDIS_URI too while the bundled job runner is on. A REDIS_URL, or a
host, port, user or TLS key left in edisyl-redis, is refused.
The password is required. The chart never generates one for your Redis — it must be your server’s — and the API refuses to start on a blank one, so a passwordless Redis needs a password before you install.
ElastiCache and Memorystore both work, provided they offer RediSearch and
RedisJSON. Use rediss:// and config.redisTls: "true" where the service
requires TLS.
Behind Sentinel
Section titled “Behind Sentinel”For a Redis behind Sentinel with no proxy in front — the configurator’s “Redis Sentinel” choice, with its “Sentinel addresses” and “Master name” — set the Sentinels and the master set name instead of a host and port. The API asks the Sentinels for the primary and follows it through a failover, giving them the same user, password and TLS setting as the data nodes:
redis: enabled: false
config: redisSentinels: "redis-sentinel-0.redis:26379,redis-sentinel-1.redis:26379" redisSentinelMaster: mymaster redisUser: default
secrets: managed: edisyl-redis: data: REDIS_PASSWORD: "password" edisyl-redis-lattice: data: LATTICE_REDIS_URL: "redis://default:password@redis-sentinel-0.redis:26379?master_name=mymaster&addr=redis-sentinel-1.redis:26379" edisyl-redis-inngest: data: # One address that follows the primary — see below. INNGEST_REDIS_URI: "redis://default:password@redis-primary.redis:6379"Both Sentinel values or neither; the addresses are comma-separated host:port
pairs. config.redisHost and config.redisPort are then optional and ignored.
The rest of the constraints:
- Lattice reads its own URL. Give
LATTICE_REDIS_URLthe Sentinel form above; its credentials reach both the Sentinels and the data nodes. - The job runner cannot authenticate to Sentinels. Inngest’s client dials
the Sentinels with no password and no TLS, so its Sentinel form
(
redis://<user>:<password>@<sentinel-0>:26379?master_set=<set>&addr=<sentinel-1>:26379) works only with Sentinels that require neither. Otherwise pointINNGEST_REDIS_URIat one address that follows the primary, such as an operator’s master Service; Inngest then reconnects after a failover rather than following it. - Bundled Redis has no Sentinel. It is one node, and the render refuses
the Sentinel values with
redis.enabled: true. - Announce hostnames. Have the Sentinels announce hostnames
(
resolve-hostnames yes,announce-hostnames yes), so a restarted pod’s new IP does not break the clients. - It needs current images. Sentinel mode needs API images that read
REDIS_SENTINELS; an older API ignores it and needsconfig.redisHost.
Object storage
Section titled “Object storage”minio: enabled: false
config: objectStorageProvider: s3 objectStorageBucket: my-edisyl-files objectStorageRegion: us-east-1 objectStorageUrl: "" objectStorageForcePathStyle: ""
secrets: managed: edisyl-object-storage: data: OBJECT_STORAGE_ACCESS_KEY_ID: "" OBJECT_STORAGE_SECRET_ACCESS_KEY: ""The provider, bucket, region and URL are configuration, under
config.objectStorage*; edisyl-object-storage holds the two keys and nothing
else. config.objectStorageProvider takes s3 or s3compatible, and the
distinction is load-bearing:
s3means real AWS S3. LeaveobjectStorageUrlandobjectStorageForcePathStyleempty, set the region and bucket, and either supply the two keys or leave them blank and use a pod IAM role.s3compatibleis for MinIO you run yourself, Ceph, Cloudflare R2, or any store reached through an explicit endpoint. It needsobjectStorageUrland both keys.objectStorageForcePathStylepicks the addressing:"true"for path-style (endpoint/bucket/key), which MinIO and Ceph need and R2 and Wasabi accept, or"false"for virtual-hosted (bucket.endpoint/key), only when the store requires it and its DNS resolves every bucket name.
Choosing s3compatible while pointing at AWS — or s3 with a URL — is the
silent misconfiguration to avoid: storage that looks wired up and writes
nowhere. The chart refuses s3 with a URL and s3compatible without one.
Leave OBJECT_STORAGE_ACCESS_KEY_ID and OBJECT_STORAGE_SECRET_ACCESS_KEY
blank to use a pod IAM role (IRSA on EKS, Workload Identity on GKE), which is
the better option where your cluster supports it.
Cloudflare R2
Section titled “Cloudflare R2”R2 is an s3compatible store reached through its S3 endpoint, with an R2 API
token’s access key pair. The provider value r2 and the CLOUDFLARE_R2_* keys
were removed in chart 0.42.0, and r2 is refused:
config: objectStorageProvider: s3compatible objectStorageBucket: my-edisyl-files objectStorageRegion: auto objectStorageUrl: https://<account-id>.r2.cloudflarestorage.com
secrets: managed: edisyl-object-storage: data: OBJECT_STORAGE_ACCESS_KEY_ID: "<r2 access key id>" OBJECT_STORAGE_SECRET_ACCESS_KEY: "<r2 secret access key>"Inngest
Section titled “Inngest”inngest: enabled: false
config: inngestBaseUrl: "https://api.inngest.com"
secrets: managed: edisyl-inngest: data: INNGEST_EVENT_KEY: "<event key>" INNGEST_SIGNING_KEY: "<signing key>"Worth doing on a cloud for a reason beyond tidiness: the bundled job runner
keeps its run history in an emptyDir, so completed runs and step results do
not survive a pod restart. See
Known limitations.
A worked example
Section titled “A worked example”Everything external, nothing bundled:
cnpg: { enabled: false }redis: { enabled: false }minio: { enabled: false }inngest: { enabled: false }
database: source: existing-secret existingSecret: { name: my-postgres-connection, key: uri }
config: domain: example.com inngestBaseUrl: "https://api.inngest.com" emailFrom: platform@example.com redisHost: redis.example.internal redisPort: "6379" redisUser: default redisTls: "true" objectStorageProvider: s3 objectStorageBucket: my-edisyl-files objectStorageRegion: us-east-1
bootstrap: adminEmails: ["admin@example.com"]
deploymentIdp: metadataUrl: "https://idp.example.com/app/saml/metadata" domains: [example.com]
edisyl: auth: database: { secretName: my-auth-db-connection, secretKey: uri }
# The gateway always runs, so it needs a database of its own.bifrost: database: source: existing-secret existingSecret: { name: bifrost-db }
secrets: provider: k8s-native managed: edisyl-redis: data: REDIS_PASSWORD: "password" edisyl-redis-lattice: data: LATTICE_REDIS_URL: "rediss://default:password@redis.example.internal:6379/1" edisyl-inngest: data: INNGEST_EVENT_KEY: "<event key>" INNGEST_SIGNING_KEY: "<signing key>" # Pod IAM role for S3, so the two object-storage keys stay blank. The auth # signing material comes from the minted file — see Secrets and signing # keys.That runs the first-party services against your own datastores: no bundled
database, cache, object store or job runner, and no CloudNativePG operator —
that is only needed when cnpg.enabled is true.
Two things still run in the cluster beyond the applications: the auth service
and the LLM gateway, both deployments of this chart rather than bundled
third-party charts, each against its own database on your Postgres. The pods
also mount small volumes — emptyDir scratch space for the applications, and
a volume for the gateway’s request logs — so “nothing bundled” means no
bundled infrastructure, not no volumes at all.
Keeping credentials out of values
Section titled “Keeping credentials out of values”Everything above puts credentials in a values file. For anything beyond a
trial, use secrets.provider: external-secrets instead and let the chart read
them from your secret manager — AWS Secrets Manager, Doppler, Vault or any
other backend the External Secrets Operator supports. The chart then generates
nothing, and every key must exist in the store up front. See
Secrets and signing keys.
Was this page helpful?
Thanks for the feedback.
