Skip to content
Install the CLI

How-to guide Adopt existing resources

Import an existing service with kix.mkImport

This guide assumes the Service already exists and its name, namespace, and port are stable. Use an import when another system owns that Service and Kix packages need its connection details.

Add an instance whose package is created with kix.mkImport:

how-to/adoption/cluster.nix
package = kix.mkImport {
kind = "Service";
apiVersion = "v1";
out = {
name = "legacy-api";
fqdn = "legacy-api.legacy.svc.${config.clusterDomain}";
port = 8080;
};
};
aliases = [ "legacyApi" ];

View source on GitHub ↗

Set kind and apiVersion to describe the existing resource. The out attributes form the interface that consuming packages receive. They must match the Service in the target cluster.

Kix verifies the imported object’s kind, API version, name, and namespace before deployment. It does not verify the fqdn or port outputs. Those values pass through unchanged, so an incorrect port can deploy successfully and fail only at runtime.

The legacyApi alias lets a package request this import with a build argument of the same name. Choose an alias that describes the dependency’s role rather than its location. If the import provides one of Kix’s registered infrastructure roles, such as dns, declare it with roles = [ "dns" ] instead. The role name also acts as the dependency key. An imported role is a weak provider, so a managed instance that claims the same role takes precedence.

Accept the alias as a package build argument and read the values you need:

how-to/adoption/client-package.nix
clientConfig = scope.mkResource {
apiVersion = "v1";
kind = "ConfigMap";
name = "legacy-api-client";
data.endpoint = "http://${legacyApi.out.fqdn}:${toString legacyApi.out.port}";
};
root = self.clientConfig;

View source on GitHub ↗

This example writes the imported endpoint into a client ConfigMap. A workload could use the same values in environment variables or generated configuration.

Evaluate the cluster before deploying it:

kix-examples/
❱ kix check how-to-adoption
 TOOL       RESULT  DETAILS                       
 eval       pass    21 manifests evaluated        
 scorecard  pass    0 errors, 14 warnings, 2 info

Confirm that Kix classifies the instance as an import:

kix-examples/
❱ kix list packages --cluster how-to-adoption
 NAME                 VERSION  STATUS               
 client               1.0.0    installed [apps]     
 helm-web             1.0.0    installed [apps]     
 legacy-api           -        import [legacy]      
 legacy-api-contract  1.0.0    installed [apps]     
 legacy-web           1.0.0    installed [apps]     
 platform-dns         -        import [kube-system] 
 platform-storage     -        import [kube-system]

Render the manifests to verify the value received by the client:

kix-examples/ output excerpt
❱ kix build how-to-adoption --output json
{
  "apiVersion": "v1",
  "kind": "ConfigMap",
  "metadata": {
    "name": "legacy-api-client",
    "namespace": "apps"
  },
  "data": {
    "endpoint": "http://legacy-api.legacy.svc.cluster.local:8080"
  }
}

Kix renders a PackageInstance marker for legacy-api, but it does not render or update the Service itself. Keep the Service in the lifecycle of the system that already owns it.

Dependency contracts that carry a login put it in out.credentials. In a cluster module that takes { ref, ... }:, kix.mkImport can point a field in out at a secret or secret-ref instance with ref.<namespace>.<instance>. Kix replaces the ref with that secret instance (so the consumer can call dep.out.credentials.out.keyRef "password" with no package changes) and orders the import marker after the secret.

Because a pod reads Secrets only from its own namespace, and Kix refuses a Secret from another namespace at build time, place the import and its secret in the consumer’s namespace with scope = "namespace":

cluster.nix
{ ref, packages, kix, ... }:
{
instances.app.rds-login = {
package = packages.secret;
config.source = ./secrets/rds-login.sops.yaml;
config.keys = [ "username" "password" ];
};
instances.app.database.package = kix.mkImport {
scope = "namespace";
out = {
host = "app.abc123.eu-west-1.rds.amazonaws.com";
port = "5432";
database = "app";
tls = true;
credentials = ref.app.rds-login;
};
};
}

This satisfies the database contract (kix.contracts.postgresDatabase) for an outside PostgreSQL server such as RDS or Cloud SQL. The same pattern works for an outside Redis cache (kix.contracts.redis, see Use a Redis server outside the cluster), an authenticated mail server (kix.contracts.smtp), or a single-issuer external OIDC provider (kix.contracts.oidc). See Connect external databases, caches, mail, and single sign-on for complete examples of each contract.