Kustomize’s default behavior is to apply resources in the order it discovers them, which can lead to race conditions where a resource that depends on another is applied too early.
Let’s see Kustomize in action with a common scenario: deploying a database before a web application that needs to connect to it.
Here’s a simple Kustomize setup:
kustomization.yaml
resources:
- namespace.yaml
- database-deployment.yaml
- database-service.yaml
- app-deployment.yaml
- app-service.yaml
database-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-database
namespace: my-app
spec:
replicas: 1
selector:
matchLabels:
app: database
template:
metadata:
labels:
app: database
spec:
containers:
- name: postgres
image: postgres:13
env:
- name: POSTGRES_DB
value: mydb
- name: POSTGRES_USER
value: user
- name: POSTGRES_PASSWORD
value: password
database-service.yaml
apiVersion: v1
kind: Service
metadata:
name: database-service
namespace: my-app
spec:
selector:
app: database
ports:
- protocol: TCP
port: 5432
targetPort: 5432
app-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
namespace: my-app
spec:
replicas: 2
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: app
image: my-custom-app:latest
env:
- name: DATABASE_URL
value: "postgresql://user:password@database-service:5432/mydb"
If you were to run kustomize build . | kubectl apply -f - with the above kustomization.yaml, the app-deployment.yaml might be applied before database-service.yaml is fully ready, causing the my-app pods to fail on startup because they can’t resolve database-service.
The problem Kustomize resource ordering solves is precisely this: ensuring that Kubernetes resources are created and updated in an order that respects their dependencies. Without explicit ordering, Kustomize relies on the order in the resources list or the order of files in a directory, which are not guaranteed to align with the actual dependencies between your Kubernetes objects. This can manifest as errors like "Service not found" or "Connection refused" when a dependent resource tries to communicate with a service that hasn’t been created or fully initialized yet.
Kustomize handles resource ordering primarily through the dependsOn field within its kustomization.yaml. This field allows you to explicitly declare that one resource must be created after another.
Here’s how you’d modify the kustomization.yaml to enforce the correct order:
kustomization.yaml
resources:
- namespace.yaml
- database-deployment.yaml
- database-service.yaml
- app-deployment.yaml
- app-service.yaml
# Explicitly define dependencies
patchesStrategicMerge:
- app-deployment-patch.yaml
app-deployment-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
namespace: my-app
spec:
template:
spec:
containers:
- name: app
env:
- name: DATABASE_URL
value: "postgresql://user:password@database-service:5432/mydb"
# This is the crucial part for ordering
initContainers:
- name: wait-for-db
image: busybox:latest
command:
- sh
- -c
- >
echo "Waiting for database...";
wget --retry-connrefused --waitretry=5 --timeout=15 -qO- http://database-service:5432/ || exit 1;
echo "Database ready!"
In this corrected setup, we’re not directly using dependsOn in the kustomization.yaml itself because dependsOn is a feature of certain Kubernetes controllers (like Argo CD or Flux CD) for managing apply order, not a native Kustomize field for generating ordered manifests. Instead, Kustomize’s power comes from its ability to patch and transform existing resources.
The app-deployment-patch.yaml introduces an initContainer to the my-app deployment. This initContainer will run before the main application containers start. It actively attempts to connect to the database-service on port 5432. If the connection fails (e.g., the service isn’t ready), the initContainer will retry. Only when the initContainer successfully connects to the database will the main application containers be allowed to start.
This pattern effectively enforces resource ordering at the application level, ensuring that the database is available and listening before the application tries to connect. The Kustomize patchesStrategicMerge directive ensures that this initContainer is added to the my-app deployment definition.
When kustomize build . is run, it merges app-deployment-patch.yaml into app-deployment.yaml, producing a single, complete deployment manifest for my-app that includes the initContainer. This generated manifest is then passed to kubectl apply.
The real benefit here is Kustomize’s composability. You can define a base database deployment and service, and then for different applications that depend on it, you can apply different patches that include specific wait logic or connection strings, all managed within Kustomize’s declarative framework. This avoids the need for external scripting or complex CI/CD pipeline logic to manage apply order for simple dependencies.
What many users don’t realize is that Kustomize itself doesn’t directly control the Kubernetes API server’s apply order. It generates static YAML. The actual ordering that matters for dependencies like this is handled by the application’s startup logic (like the initContainer waiting for the service) or by advanced GitOps tools that can interpret dependency information. Kustomize’s role is to construct the manifests that enable this ordered behavior.
The next challenge is managing complex dependencies across multiple namespaces or external resources.