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.

Want structured learning?

Take the full Kustomize course →