06. Use a local package and service-to-service dependency
So far, each tutorial cluster has had one service. This tutorial adds the first service-to-service relationship.
You will run the 06-service-dep example from kix-examples: an nginx
gateway proxies to a backend echo-server. The gateway package asks Kix for a
backend dependency, then uses the backend’s public out values to build its
nginx config. You will use and inspect the local package here; the next
tutorial will spend more time on package anatomy.
Prerequisites
Section titled “Prerequisites”- Finish the basic graph and deploy loop tutorial.
- Keep the same
kix-examplescheckout and kind cluster.
This tutorial returns to a checked-in example, so you do not need to keep the files from tutorial 04 staged.
What This Example Builds
Section titled “What This Example Builds”Open:
tutorials/06-service-dep/cluster.nixtutorials/06-service-dep/reverse-proxy-package.nixThe cluster has two instances in the tutorial-06 namespace:
| Instance | Package | Job |
|---|---|---|
backend | packages.echo-server | answers HTTP with a fixed message |
gateway | local reverse-proxy-package.nix | proxies HTTP traffic to backend |
When you curl the gateway, the request path is:
localhost:8080 -> gateway pod -> backend Service -> backend podNo Ingress or LoadBalancer is involved. This is just two ClusterIP Services and one dependency edge.
Import A Local Package
Section titled “Import A Local Package”The previous tutorials used a catalog package from kixpkgs:
packages.echo-server.
This example also imports a package from the example folder:
let reverseProxyPackage = import ./reverse-proxy-package.nix;inlet ... in ... creates a local binding. Here it means: evaluate
./reverse-proxy-package.nix once, call it reverseProxyPackage, and use that
name later in the cluster definition.
This keeps cluster composition and package implementation separate:
cluster.nixsays which package instances are installed;reverse-proxy-package.nixsays how the local package builds Kubernetes resources.
Install Two Instances
Section titled “Install Two Instances”The cluster installs two package instances:
backend = { package = packages.echo-server; config = { message = "Hello from the backend!"; };};
gateway = { package = reverseProxyPackage;};The backend instance is a normal echo-server. The gateway instance uses
the local reverse-proxy package.
Notice what the gateway instance does not say: it does not include a Service
name, namespace, DNS name, or port for the backend. The package asks for a
backend dependency, and Kix resolves that request to the instance named
backend in the same namespace.
That same-namespace name match is the first dependency resolution rule you need to learn. Later tutorials will make cross-namespace dependencies explicit.
Ask For A Dependency In The Package
Section titled “Ask For A Dependency In The Package”Open tutorials/06-service-dep/reverse-proxy-package.nix. The package’s
build function receives named arguments from Kix:
build = { self, scope, kix, backend, ... }:Do not try to unpack every argument yet. The important new one is backend.
That is the dependency. Inside the package,
backend exposes an out API with values the package can use safely.
For now, treat out as the public interface another instance exposes. It gives
you names, DNS names, ports, selectors, and similar values without hardcoding
them.
Use The Backend’s out Values
Section titled “Use The Backend’s out Values”The reverse proxy writes an nginx config into a ConfigMap:
configmap = scope.mkResource { apiVersion = "v1"; kind = "ConfigMap"; inherit name; data."default.conf" = '' server { listen 80; location /healthz { default_type text/plain; return 200 'ok\n'; } location / { proxy_pass http://${backend.out.fqdn}:${backend.out.port}/; } } ''; };The important line is the proxy_pass target:
proxy_pass http://${backend.out.fqdn}:${backend.out.port}/;That line does two jobs:
- it builds a real nginx upstream URL using the backend Service DNS name and port;
- it consumes the backend’s public values instead of repeating Kubernetes names by hand.
You do not need to hand-copy the backend Service name into the gateway config. The package dependency creates the relationship; the package code reads the backend’s public interface.
Check The Example
Section titled “Check The Example”Before deploying, ask Kix to evaluate and check the cluster:
❱ kix check 06-service-dep
TOOL RESULT DETAILS
eval pass 14 manifests evaluated
kubeconform pass skipped (this validation tool is not yet integrated with Kix)
pluto pass skipped (this validation tool is not yet integrated with Kix)
kyverno pass skipped (this validation tool is not yet integrated with Kix)
scorecard pass 0 errors, 9 warnings, 2 info Deploy The Example
Section titled “Deploy The Example”Deploy the example:
❱ kix deploy 06-service-dep -y Show output
Building cluster '06-service-dep'...
Cluster 06-service-dep: 14 manifests
Connecting to cluster...
No previous activation on cluster — first deploy.
Reading live cluster state...
_cluster
~ cluster-level resources (1 added, 2 removed)
tutorial-02
- hello-world 1.27 (3 resources)
tutorial-04
- hello-world 1.27 (3 resources)
tutorial-06
+ backend 1.27 (3 resources)
+ gateway 0.0.1 (3 resources)
Plan: cluster-level changes, 2 added, 2 removed, 1 unchanged
Resources: 3 real content, 0 dep-affected
10 orphaned (kept; pass --prune to delete)
- Deployment/hello-world@tutorial-02
- Namespace/tutorial-02
- ConfigMap/hello-world@tutorial-04
- ConfigMap/hello-world@tutorial-02
- PackageInstance/hello-world@tutorial-02
- Service/hello-world@tutorial-04
- PackageInstance/hello-world@tutorial-04
- Service/hello-world@tutorial-02
- Deployment/hello-world@tutorial-04
- Namespace/tutorial-04
plan: 14 nodes
+ Namespace/tutorial-06 created
✔ Namespace/tutorial-06 ready
+ ConfigMap/backend@tutorial-06 created
✔ ConfigMap/backend@tutorial-06 ready
+ Deployment/backend@tutorial-06 created
✔ Deployment/backend@tutorial-06 ready
+ Service/backend@tutorial-06 created
✔ Service/backend@tutorial-06 ready
+ PackageInstance/backend@tutorial-06 created
✔ PackageInstance/backend@tutorial-06 ready
+ ConfigMap/gateway@tutorial-06 created
✔ ConfigMap/gateway@tutorial-06 ready
+ Deployment/gateway@tutorial-06 created
✔ Deployment/gateway@tutorial-06 ready
+ Service/gateway@tutorial-06 created
✔ Service/gateway@tutorial-06 ready
+ PackageInstance/gateway@tutorial-06 created
✔ PackageInstance/gateway@tutorial-06 ready
+ Activation/06-service-dep-gfqhjshmmm8b created
✔ Activation/06-service-dep-gfqhjshmmm8b ready
prune: 10 orphaned resources kept (warn-only; pass --prune to delete)
- PackageInstance/hello-world@tutorial-04
- Service/hello-world@tutorial-04
- Deployment/hello-world@tutorial-04
- ConfigMap/hello-world@tutorial-04
- Namespace/tutorial-04
- PackageInstance/hello-world@tutorial-02
- Service/hello-world@tutorial-02
- Deployment/hello-world@tutorial-02
- ConfigMap/hello-world@tutorial-02
- Namespace/tutorial-02
• activation '06-service-dep-gfqhjshmmm8b' → Active
Deploy complete: 10 created, 0 configured, 4 unchanged, 0 failed Port-forward to the gateway:
❱ kix pf 06-service-dep gateway 8080:80 In another terminal:
❱ curl http://localhost:8080/
Hello from the backend! The response comes from the backend pod. The gateway only proxies the request.
Stop the port-forward with Ctrl-C.
Inspect The Wiring
Section titled “Inspect The Wiring”Ask Kix to show the dependency graph:
❱ kix graph 06-service-dep --format tree Show output
CustomResourceDefinition/activations.kix.run
└── Activation/06-service-dep-ijyb6jl72rsh
CustomResourceDefinition/packageinstances.kix.run
├── PackageInstance/gateway@tutorial-06
│ └── Activation/06-service-dep-ijyb6jl72rsh
├── PackageInstance/backend@tutorial-06
│ └── Activation/06-service-dep-ijyb6jl72rsh
└── PackageInstance/platform-dns@kube-system (import)
├── Activation/06-service-dep-ijyb6jl72rsh
└── Deployment/gateway@tutorial-06
└── Service/gateway@tutorial-06
└── PackageInstance/gateway@tutorial-06
└── Activation/06-service-dep-ijyb6jl72rsh
Namespace/kube-system
└── PackageInstance/platform-dns@kube-system (import)
├── Activation/06-service-dep-ijyb6jl72rsh
└── Deployment/gateway@tutorial-06
└── Service/gateway@tutorial-06
└── PackageInstance/gateway@tutorial-06
└── Activation/06-service-dep-ijyb6jl72rsh
Namespace/tutorial-06
├── Service/gateway@tutorial-06
│ └── PackageInstance/gateway@tutorial-06
│ └── Activation/06-service-dep-ijyb6jl72rsh
├── Service/backend@tutorial-06
│ ├── ConfigMap/gateway@tutorial-06
│ │ └── Deployment/gateway@tutorial-06
│ │ └── Service/gateway@tutorial-06
│ │ └── PackageInstance/gateway@tutorial-06
│ │ └── Activation/06-service-dep-ijyb6jl72rsh
│ └── PackageInstance/backend@tutorial-06
│ └── Activation/06-service-dep-ijyb6jl72rsh
├── ConfigMap/gateway@tutorial-06
│ └── Deployment/gateway@tutorial-06
│ └── Service/gateway@tutorial-06
│ └── PackageInstance/gateway@tutorial-06
│ └── Activation/06-service-dep-ijyb6jl72rsh
├── ConfigMap/backend@tutorial-06
│ └── Deployment/backend@tutorial-06
│ └── Service/backend@tutorial-06
│ ├── ConfigMap/gateway@tutorial-06
│ │ └── Deployment/gateway@tutorial-06
│ │ └── Service/gateway@tutorial-06
│ │ └── PackageInstance/gateway@tutorial-06
│ │ └── Activation/06-service-dep-ijyb6jl72rsh
│ └── PackageInstance/backend@tutorial-06
│ └── Activation/06-service-dep-ijyb6jl72rsh
├── PackageInstance/gateway@tutorial-06
│ └── Activation/06-service-dep-ijyb6jl72rsh
├── PackageInstance/backend@tutorial-06
│ └── Activation/06-service-dep-ijyb6jl72rsh
├── Deployment/gateway@tutorial-06
│ └── Service/gateway@tutorial-06
│ └── PackageInstance/gateway@tutorial-06
│ └── Activation/06-service-dep-ijyb6jl72rsh
└── Deployment/backend@tutorial-06
└── Service/backend@tutorial-06
├── ConfigMap/gateway@tutorial-06
│ └── Deployment/gateway@tutorial-06
│ └── Service/gateway@tutorial-06
│ └── PackageInstance/gateway@tutorial-06
│ └── Activation/06-service-dep-ijyb6jl72rsh
└── PackageInstance/backend@tutorial-06
└── Activation/06-service-dep-ijyb6jl72rsh
14 resources, 24 dependencies Look for the gateway instance and the backend resources it depends on.
You can also render the manifests and inspect the generated nginx config:
❱ kix build 06-service-dep Show output
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
kix.run/identity-hash: gvrlspcfgh9a2qnsx5kqxw9f4l3x52jj
kix.run/package: _cluster
kix.run/package-namespace: _cluster
labels:
app.kubernetes.io/managed-by: kix
name: activations.kix.run
spec:
group: kix.run
names:
kind: Activation
listKind: ActivationList
plural: activations
singular: activation
scope: Cluster
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
properties:
apiVersion:
type: string
kind:
type: string
metadata:
type: object
spec:
type: object
x-kubernetes-preserve-unknown-fields: true
status:
type: object
x-kubernetes-preserve-unknown-fields: true
type: object
served: true
storage: true
subresources:
status: {}
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
kix.run/identity-hash: pk4hih6jm4yj336rlaqk2qycz2k46xvs
kix.run/package: _cluster
kix.run/package-namespace: _cluster
labels:
app.kubernetes.io/managed-by: kix
name: packageinstances.kix.run
spec:
group: kix.run
names:
kind: PackageInstance
listKind: PackageInstanceList
plural: packageinstances
singular: packageinstance
scope: Namespaced
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
properties:
apiVersion:
type: string
kind:
type: string
metadata:
type: object
spec:
type: object
x-kubernetes-preserve-unknown-fields: true
status:
type: object
x-kubernetes-preserve-unknown-fields: true
type: object
served: true
storage: true
subresources:
status: {}
---
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
kix.run/depends-on: '1wz2372bgznyyc56m28alsibkgfcq4dc,wlzmynapx327962105rsvvnmaqyw2vyn'
kix.run/identity-hash: cqx7f7f2w1pa7llx2zrk7p3qb374if76
kix.run/package: backend
kix.run/package-namespace: tutorial-06
labels:
app.kubernetes.io/instance: backend
app.kubernetes.io/managed-by: kix
app.kubernetes.io/name: backend
name: backend
namespace: tutorial-06
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/instance: backend
app.kubernetes.io/name: backend
template:
metadata:
labels:
app.kubernetes.io/instance: backend
app.kubernetes.io/name: backend
spec:
containers:
- image: nginxinc/nginx-unprivileged:1.27-alpine
imagePullPolicy: IfNotPresent
livenessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: 8080
periodSeconds: 10
name: nginx
ports:
- containerPort: 8080
name: http
protocol: TCP
readinessProbe:
failureThreshold: 1
httpGet:
path: /healthz
port: 8080
periodSeconds: 5
resources:
limits:
cpu: '100m'
memory: '64Mi'
requests:
cpu: '50m'
memory: '32Mi'
securityContext:
runAsNonRoot: true
volumeMounts:
- mountPath: /etc/nginx/conf.d
name: config
readOnly: true
volumes:
- configMap:
name: backend
name: config
---
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
kix.run/depends-on: '1wz2372bgznyyc56m28alsibkgfcq4dc,by3dv0xzvky726i7a8b27mmqv46rgkhy,requires:jh345cnnffy1bnj4cz5x0hwq6ibxvkkr'
kix.run/identity-hash: d8lsi37jfr7i49svzfc52nqgzpjb97i3
kix.run/package: gateway
kix.run/package-namespace: tutorial-06
labels:
app.kubernetes.io/instance: gateway
app.kubernetes.io/managed-by: kix
app.kubernetes.io/name: gateway
name: gateway
namespace: tutorial-06
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/instance: gateway
app.kubernetes.io/name: gateway
template:
metadata:
labels:
app.kubernetes.io/instance: gateway
app.kubernetes.io/name: gateway
spec:
containers:
- image: nginx:1.27-alpine
imagePullPolicy: IfNotPresent
name: nginx
ports:
- containerPort: 80
name: http
protocol: TCP
readinessProbe:
httpGet:
path: /healthz
port: 80
periodSeconds: 5
volumeMounts:
- mountPath: /etc/nginx/conf.d
name: config
readOnly: true
volumes:
- configMap:
name: gateway
name: config
---
apiVersion: kix.run/v1alpha1
kind: Activation
metadata:
annotations:
kix.run/built-via: /nix/store/hnlf8ghnyr7khhvgkhvjz1alnscqiir6-k8s-activation-06-service-dep
kix.run/depends-on: hk1cigl67rdmdp9yi6l0cy6dd09n874y,jh345cnnffy1bnj4cz5x0hwq6ibxvkkr,rrr57m2k9ihrv8pppy2y6cfd60yz6r8x,requires:gvrlspcfgh9a2qnsx5kqxw9f4l3x52jj
kix.run/identity-hash: ijyb6jl72rshzxfx2y87xrr1998hkjan
kix.run/package: _cluster
kix.run/package-namespace: _cluster
labels:
app.kubernetes.io/managed-by: kix
kix.run/cluster: '06-service-dep'
name: '06-service-dep-ijyb6jl72rsh'
spec:
instances:
kube-system:
platform-dns: platform-dns
tutorial-06:
backend: backend
gateway: gateway
---
apiVersion: kix.run/v1alpha1
kind: PackageInstance
metadata:
annotations:
kix.run/depends-on: sj0fv5bs2hnh9zcqdh62ph34l2wm0b1m,requires:pk4hih6jm4yj336rlaqk2qycz2k46xvs
kix.run/identity-hash: jh345cnnffy1bnj4cz5x0hwq6ibxvkkr
kix.run/import-apiversion: apps/v1
kix.run/import-fqdn: kube-dns.kube-system.svc.cluster.local
kix.run/import-kind: Deployment
kix.run/import-name: coredns
kix.run/mode: import
kix.run/package: _cluster
kix.run/package-namespace: _cluster
labels:
app.kubernetes.io/instance: platform-dns
app.kubernetes.io/managed-by: kix
name: platform-dns
namespace: kube-system
spec:
instanceName: platform-dns
mode: import
namespaceName: kube-system
---
apiVersion: kix.run/v1alpha1
kind: PackageInstance
metadata:
annotations:
kix.run/depends-on: '1wz2372bgznyyc56m28alsibkgfcq4dc,7iprxwy73s26bjhm4cyx5agmpz2q14lz,requires:pk4hih6jm4yj336rlaqk2qycz2k46xvs'
kix.run/identity-hash: rrr57m2k9ihrv8pppy2y6cfd60yz6r8x
kix.run/package: _cluster
kix.run/package-namespace: _cluster
kix.run/root: backend
labels:
app.kubernetes.io/instance: backend
app.kubernetes.io/managed-by: kix
name: backend
namespace: tutorial-06
spec:
instanceName: backend
namespaceName: tutorial-06
version: '1.27'
---
apiVersion: kix.run/v1alpha1
kind: PackageInstance
metadata:
annotations:
kix.run/depends-on: '1wz2372bgznyyc56m28alsibkgfcq4dc,fsvvbizzzlqmzya0ccprxxci2dfqym28,requires:pk4hih6jm4yj336rlaqk2qycz2k46xvs'
kix.run/identity-hash: hk1cigl67rdmdp9yi6l0cy6dd09n874y
kix.run/package: _cluster
kix.run/package-namespace: _cluster
kix.run/root: gateway
labels:
app.kubernetes.io/instance: gateway
app.kubernetes.io/managed-by: kix
name: gateway
namespace: tutorial-06
spec:
instanceName: gateway
namespaceName: tutorial-06
version: '0.0.1'
---
apiVersion: v1
data:
default.conf: |
server {
listen 8080;
location / {
default_type text/plain;
return 200 'Hello from the backend!\n';
}
location /healthz {
default_type text/plain;
return 200 'ok\n';
}
}
kind: ConfigMap
metadata:
annotations:
kix.run/depends-on: '1wz2372bgznyyc56m28alsibkgfcq4dc'
kix.run/identity-hash: wlzmynapx327962105rsvvnmaqyw2vyn
kix.run/package: backend
kix.run/package-namespace: tutorial-06
labels:
app.kubernetes.io/instance: backend
app.kubernetes.io/managed-by: kix
name: backend
namespace: tutorial-06
---
apiVersion: v1
data:
default.conf: |
server {
listen 80;
location /healthz {
default_type text/plain;
return 200 'ok\n';
}
location / {
proxy_pass http://backend.tutorial-06.svc.cluster.local:80/;
}
}
kind: ConfigMap
metadata:
annotations:
kix.run/depends-on: '1wz2372bgznyyc56m28alsibkgfcq4dc,7iprxwy73s26bjhm4cyx5agmpz2q14lz'
kix.run/identity-hash: by3dv0xzvky726i7a8b27mmqv46rgkhy
kix.run/package: gateway
kix.run/package-namespace: tutorial-06
labels:
app.kubernetes.io/instance: gateway
app.kubernetes.io/managed-by: kix
name: gateway
namespace: tutorial-06
---
apiVersion: v1
kind: Namespace
metadata:
annotations:
kix.run/identity-hash: sj0fv5bs2hnh9zcqdh62ph34l2wm0b1m
kix.run/package: _cluster
kix.run/package-namespace: _cluster
labels:
app.kubernetes.io/managed-by: kix
kubernetes.io/metadata.name: kube-system
name: kube-system
---
apiVersion: v1
kind: Namespace
metadata:
annotations:
kix.run/identity-hash: '1wz2372bgznyyc56m28alsibkgfcq4dc'
kix.run/package: _cluster
kix.run/package-namespace: _cluster
labels:
app.kubernetes.io/managed-by: kix
kubernetes.io/metadata.name: tutorial-06
name: tutorial-06
---
apiVersion: v1
kind: Service
metadata:
annotations:
kix.run/depends-on: '1wz2372bgznyyc56m28alsibkgfcq4dc,cqx7f7f2w1pa7llx2zrk7p3qb374if76'
kix.run/identity-hash: '7iprxwy73s26bjhm4cyx5agmpz2q14lz'
kix.run/package: backend
kix.run/package-namespace: tutorial-06
labels:
app.kubernetes.io/instance: backend
app.kubernetes.io/managed-by: kix
name: backend
namespace: tutorial-06
spec:
ports:
- name: http
port: 80
protocol: TCP
targetPort: 8080
selector:
app.kubernetes.io/instance: backend
app.kubernetes.io/name: backend
type: ClusterIP
---
apiVersion: v1
kind: Service
metadata:
annotations:
kix.run/depends-on: '1wz2372bgznyyc56m28alsibkgfcq4dc,d8lsi37jfr7i49svzfc52nqgzpjb97i3'
kix.run/identity-hash: fsvvbizzzlqmzya0ccprxxci2dfqym28
kix.run/package: gateway
kix.run/package-namespace: tutorial-06
labels:
app.kubernetes.io/instance: gateway
app.kubernetes.io/managed-by: kix
name: gateway
namespace: tutorial-06
spec:
ports:
- name: http
port: 80
protocol: TCP
targetPort: 80
selector:
app.kubernetes.io/instance: gateway
app.kubernetes.io/name: gateway
type: ClusterIP In the gateway ConfigMap, the proxy_pass URL should contain the backend’s
cluster DNS name. The package code did not hardcode that name; it came from
backend.out.fqdn.
Try Breaking The Name Match
Section titled “Try Breaking The Name Match”In tutorials/06-service-dep/cluster.nix, rename the backend instance to
api:
api = { package = packages.echo-server; config = { message = "Hello from the backend!"; };};Then run:
❱ kix check 06-service-dep Show output
TOOL RESULT DETAILS
eval fail could not read the built resources and their dependencies: nix build failed: warning: not writing modified lock file of flake 'path:kix-examples':
• Updated input 'kixpkgs':
'git+https://github.com/kix-run/kixpkgs?ref=refs/heads/main&rev=9dcf5b3e33b728ef3fc76a693e4feda2921b1913' (2026-09-10)
→ 'path:/nix/store/1m3ijw2kvyj8z7cnnjac5h4qpl21wr1b-source/docs/..?lastModified=0&narHash=sha256-UxPEEja%2BlQ7yUWkJDR9yAydETmMgbnf8eRZ8%2Bx6m4%2BY%3D' (1970-01-01)
error:
… while calling the 'derivationStrict' builtin
at <nix/derivation-internal.nix>:37:12:
36|
37| strict = derivationStrict drvAttrs;
| ^
38|
… while evaluating the derivation attribute 'name'
at /nix/store/4rg99msfmkk9gppakagpgkzcixwg7yxq-source/pkgs/stdenv/generic/make-derivation.nix:624:11:
623| derivationArg = removeAttrs attrs removedOrReplacedAttrNames // {
624| ${if (attrs ? name || (attrs ? pname && attrs ? version)) then "name" else null} =
| ^
625| let
(stack trace truncated; use '--show-trace' to show the full, detailed trace)
error: dependency 'backend' was not found for namespace 'tutorial-06'. Add an instance with that name or alias, or set deps.backend explicitly.
(exit code: 1) Kix should fail because the gateway package asks for backend, but there is no
same-namespace instance with that name anymore.
Change api back to backend before continuing. Later tutorials assume this
example is back in its working state.
What You Learned
Section titled “What You Learned”You added the first real edge to the tutorial path:
- a cluster can import a local package;
- an instance installs that local package just like a catalog package;
- a package can ask for another instance by argument name;
- Kix resolves that argument to a matching same-namespace instance;
outlets one package use another package’s public values without hardcoded Service names.
The next dependency tutorial puts the two services in different namespaces and
uses ref to make the wiring explicit.