kubectl
environment variables
Kubernetes
yaml
configuration

pass environment variable when use kubectl apply -f file.yaml

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

kubectl apply -f file.yaml does not expand shell variables inside YAML by itself. If you want one manifest to behave differently across environments, you need a preprocessing step or a proper templating tool. The right choice depends on how much substitution logic you need and how much safety you want around missing values.

Why kubectl apply Does Not Expand Variables

Kubernetes manifests are plain YAML files. kubectl reads them as data, not as shell scripts, so a value like $IMAGE_TAG is not expanded automatically.

If you run:

yaml
1apiVersion: apps/v1
2kind: Deployment
3metadata:
4  name: demo
5spec:
6  template:
7    spec:
8      containers:
9        - name: app
10          image: my-app:$IMAGE_TAG

then kubectl apply -f deployment.yaml will send the literal string my-app:$IMAGE_TAG unless you substitute it first.

Use envsubst for Simple Substitution

For straightforward shell-variable replacement, envsubst is the simplest approach.

bash
1export IMAGE_TAG=1.4.2
2export APP_ENV=staging
3
4envsubst < deployment.yaml | kubectl apply -f -

Example manifest:

yaml
1apiVersion: v1
2kind: ConfigMap
3metadata:
4  name: app-config
5data:
6  APP_ENV: "$APP_ENV"
7---
8apiVersion: apps/v1
9kind: Deployment
10metadata:
11  name: demo
12spec:
13  replicas: 1
14  selector:
15    matchLabels:
16      app: demo
17  template:
18    metadata:
19      labels:
20        app: demo
21    spec:
22      containers:
23        - name: app
24          image: my-app:$IMAGE_TAG

This works well when your needs are limited to direct string substitution.

Restrict Which Variables Are Expanded

Blind substitution can be risky in larger files. You can restrict envsubst to specific variables:

bash
envsubst '$IMAGE_TAG $APP_ENV' < deployment.yaml | kubectl apply -f -

This avoids unexpected replacements when other $NAME strings appear in the file.

Fail Fast on Missing Variables

One weakness of plain envsubst is that unset variables become empty strings. Add checks before rendering:

bash
1#!/usr/bin/env bash
2set -euo pipefail
3
4: "${IMAGE_TAG:?IMAGE_TAG is required}"
5: "${APP_ENV:?APP_ENV is required}"
6
7envsubst '$IMAGE_TAG $APP_ENV' < deployment.yaml | kubectl apply -f -

This prevents deploying broken manifests like image: my-app:.

Use kubectl create configmap and secret When Values Belong in Kubernetes

If the goal is to inject runtime environment variables into pods, do not always bake them into templated YAML. Often it is better to store them in a ConfigMap or Secret.

bash
kubectl create configmap app-config \
  --from-literal=APP_ENV=staging \
  --dry-run=client -o yaml | kubectl apply -f -

Then reference it from the pod spec:

yaml
envFrom:
  - configMapRef:
      name: app-config

This keeps deployment logic and runtime configuration separate.

When to Use Helm or Kustomize Instead

If your manifests need conditionals, loops, overlays, or environment-specific patches, stop relying on shell substitution and use proper tooling.

Helm is better when:

  • You need reusable packaged templates.
  • You need values files per environment.

Kustomize is better when:

  • You want base manifests plus small overlays.
  • You want native kubectl compatibility.

Shell substitution is fine for simple pipelines, but it does not scale cleanly to complex deployment logic.

CI Usage Pattern

In CI, keep substitution explicit and visible in logs without printing secrets.

bash
export IMAGE_TAG="$GIT_COMMIT_SHA"
envsubst '$IMAGE_TAG' < deployment.yaml | kubectl apply -f -

For sensitive values, prefer Kubernetes Secret resources and secret-management tooling rather than direct shell expansion into manifests.

Common Pitfalls

  • Expecting kubectl apply to expand $VAR automatically.
  • Using envsubst without checking required variables first.
  • Substituting secrets directly into logs or build output.
  • Using shell substitution for configuration that really belongs in ConfigMap or Secret.
  • Stretching simple substitution too far when Helm or Kustomize would be clearer.

Summary

  • 'kubectl apply -f does not perform shell-variable expansion.'
  • 'envsubst is the simplest way to substitute environment variables before apply.'
  • Restrict expanded variables and fail fast on missing required values.
  • Use ConfigMap and Secret for runtime configuration instead of forcing everything into templates.
  • Move to Helm or Kustomize when manifest logic becomes more than simple substitution.

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.