Skip to content
Install the CLI

How-to guide Adopt existing resources

Connect external databases, caches, mail, and single sign-on

Catalog applications such as grafana, harbor, immich, keycloak, metabase, n8n, plausible, and vaultwarden connect to databases, Redis caches, mail servers, and OpenID Connect (OIDC) providers through shared dependency contracts (kix.contracts.*). When one of those backing services runs outside Kix (such as Amazon RDS, ElastiCache, Mailgun, or an external OIDC provider), declare a kix.mkImport instance that publishes the contract’s out fields.

Because Kubernetes pods can only read Secret objects in their own namespace, place any import that carries a login (out.credentials) in the consuming application’s namespace beside its secret and set scope = "namespace". Include { ref, ... }: in the cluster module header so ref.<namespace>.<instance> is in scope.

Connect an external PostgreSQL database (database)

Section titled “Connect an external PostgreSQL database (database)”

Applications that require PostgreSQL take a database dependency checked against kix.contracts.postgresDatabase. Declare a secret or secret-ref instance with keys username and password, and reference it from out.credentials on a namespaced database import:

clusters/test-doc-how-tos.nix
instances.contracts-rds = {
rds-login = {
package = packages.secret-ref;
config.keys = [
"username"
"password"
];
};
database.package = kix.mkImport {
scope = "namespace";
out = {
host = "app.abc123.eu-west-1.rds.amazonaws.com";
port = "5432";
database = "metabase";
tls = true;
credentials = ref.contracts-rds.rds-login;
};
};
encryption-key = {
package = packages.secret-ref;
aliases = [ "encryptionKey" ];
config.keys = [ "key" ];
};
metabase.package = packages.metabase;
};

View source on GitHub ↗

port must be a string (such as "5432"). If the consuming package requires specific PostgreSQL extensions (for example, immich), list the extensions installed on the external server in out.extensions.

Applications that use Redis (harbor, immich, and n8n) take a valkey dependency checked against kix.contracts.redis. Declare a secret with key password and reference it from out.credentials on a namespaced valkey import:

clusters/test-doc-how-tos.nix
instances.contracts-redis = {
redis-login = {
package = packages.secret-ref;
config.keys = [ "password" ];
};
valkey.package = kix.mkImport {
scope = "namespace";
out = {
host = "app-cache.abc123.euw1.cache.amazonaws.com";
port = "6379";
tls = true;
credentials = ref.contracts-redis.redis-login;
};
};

View source on GitHub ↗

Omit credentials and scope = "namespace" when the Redis server takes no password. Set tls = true when the server requires TLS (n8n connects over TLS when tls = true, while harbor and immich refuse a TLS-only Redis server).

Applications that send email (grafana, immich, keycloak, plausible, and vaultwarden) take an optional smtp dependency checked against kix.contracts.smtp.

When an SMTP relay requires no login, a single kix.mkImport with the default scope = "cluster" can serve every namespace in the cluster:

clusters/test-doc-how-tos.nix
instances.mail.smtp.package = kix.mkImport {
out = {
host = "relay.internal.example.org";
port = "587";
tls = "starttls";
};
};

View source on GitHub ↗

An external server (external = true, the default) must set tls to "starttls" or "implicit". Set external = false only when the relay runs inside the cluster.

When the SMTP server requires a login, declare a secret with keys username and password in the application’s namespace, and place the smtp import beside it with scope = "namespace":

clusters/test-doc-how-tos.nix
smtp-login = {
package = packages.secret-ref;
config.keys = [
"username"
"password"
];
};
smtp.package = kix.mkImport {
scope = "namespace";
out = {
host = "smtp.mailgun.org";
port = "465";
tls = "implicit";
credentials = ref.vault.smtp-login;
};
};
vaultwarden = {
package = packages.vaultwarden;
config = {
domain = "https://vault.example.org";
mail.from = "[email protected]";
};
};

View source on GitHub ↗

Set config.mail.from on the application instance when you want to override the default out.from address declared on the smtp import. For plausible and immich, an authenticated smtp server must use tls = "implicit" (usually port "465"). For grafana, tls = "implicit" is only supported on port "465".

Connect OpenID Connect single sign-on (oidc)

Section titled “Connect OpenID Connect single sign-on (oidc)”

Applications that support OpenID Connect (grafana, harbor, and immich) take an optional oidc dependency checked against kix.contracts.oidc. Single sign-on is enabled automatically when oidc is bound.

Multi-realm provider (keycloak with oidc-client)

Section titled “Multi-realm provider (keycloak with oidc-client)”

When a multi-realm provider such as keycloak runs in the cluster (or is imported via kix.contracts.oidcProvider), add an oidc-client instance and an oidcClient secret (keys = [ "clientId" "clientSecret" ]) in the application’s namespace:

clusters/test-doc-how-tos.nix
grafana-oidc = {
package = packages.secret-ref;
aliases = [ "oidcClient" ];
config.keys = [
"clientId"
"clientSecret"
];
};
oidc = {
package = packages.oidc-client;
deps.oidcProvider = ref.auth.keycloak;
config.realm = "main";
};
grafana = {
package = packages.grafana;
config = {
ingress.host = "grafana.example.org";
oidc.autoLogin = true;
};
};

View source on GitHub ↗

packages.oidc-client has meta.scope = "namespace" and meta.defaultAliases = [ "oidc" ], so the application in that namespace binds to it automatically.

When your external identity provider has a single issuer URL, declare a kix.mkImport directly in the application’s namespace with scope = "namespace" and credentials = ref.<namespace>.<secret>:

clusters/test-doc-how-tos.nix
instances.photos = {
immich-oidc = {
package = packages.secret-ref;
config.keys = [
"clientId"
"clientSecret"
];
};
oidc.package = kix.mkImport {
scope = "namespace";
out = {
issuerUrl = "https://login.example.org";
credentials = ref.photos.immich-oidc;
};
};
};

View source on GitHub ↗

If the consuming application does not use OIDC discovery (such as grafana), also set out.endpoints = { authorizationUrl = "..."; tokenUrl = "..."; userinfoUrl = "..."; }; on the oidc import.

Run kix check to verify that every imported out attribute set and referenced secret satisfies the consumer’s contract:

❱ kix check doc-how-tos

If a required contract field or secret key is missing, or if a TLS or scope constraint is violated, evaluation stops and prints the instance name, the contract name, and the missing field or key.