Skaffold’s templating system can feel like magic, but it’s actually just a sophisticated form of text substitution that happens before your Kubernetes manifests ever hit kubectl apply.
Let’s see it in action. Imagine you have a simple deployment manifest:
# deploy/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app-{{ .Values.env }}
spec:
replicas: {{ .Values.replicas }}
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
env: {{ .Values.env }}
spec:
containers:
- name: my-app
image: my-repo/my-app:{{ .Values.tag }}
ports:
- containerPort: 8080
And you want to configure it for different environments. You’d typically define your values in a skaffold.yaml:
# skaffold.yaml
apiVersion: skaffold/v2
kind: Config
deploy:
kubectl:
manifests:
- deploy/deployment.yaml
build:
local: {}
profiles:
- name: dev
activation:
- kubeContext: dev
build:
artifacts:
my-app:
image: my-repo/my-app
tagPolicy:
envTemplate:
template: "{{.BUILD_ID}}"
deploy:
helm:
releases:
- name: my-app
chartPath: deploy
valuesFiles:
- deploy/values-dev.yaml
- name: prod
activation:
- kubeContext: prod
build:
artifacts:
my-app:
image: my-repo/my-app
tagPolicy:
envTemplate:
template: "{{.BUILD_ID}}"
deploy:
helm:
releases:
- name: my-app
chartPath: deploy
valuesFiles:
- deploy/values-prod.yaml
And your values files:
# deploy/values-dev.yaml
env: development
replicas: 1
tag: latest
# deploy/values-prod.yaml
env: production
replicas: 3
tag: v1.2.0
When you run skaffold dev --profile dev, Skaffold will process deploy/deployment.yaml. It sees the {{ .Values.env }}, {{ .Values.replicas }}, and {{ .Values.tag }} placeholders. It then looks at deploy/values-dev.yaml and deploy/values-prod.yaml (and any other Helm or Kustomize configurations you might have), resolves the values for the active profile (dev in this case), and substitutes them into the manifest.
The final manifest sent to kubectl apply for the dev profile would look like this:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app-development
spec:
replicas: 1
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
env: development
spec:
containers:
- name: my-app
image: my-repo/my-app:latest
ports:
- containerPort: 8080
If you switched to skaffold dev --profile prod, the generated manifest would be:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app-production
spec:
replicas: 3
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
env: production
spec:
containers:
- name: my-app
image: my-repo/my-app:v1.2.0
ports:
- containerPort: 8080
This templating engine is based on Go’s text/template package. This means you have access to all its features: functions, control flow (if/else, range), and the ability to define your own functions. This is powerful for generating complex configurations dynamically.
The envTemplate tag policy for build artifacts is a special case. It allows you to inject build-time information (like the Git commit hash or a simple build ID) directly into your image tag, ensuring that your deployed images always match what was just built by Skaffold. For example, {{.BUILD_ID}} will be replaced by Skaffold’s internal build ID for that specific Skaffold run.
What most people don’t realize is that Skaffold’s templating can also directly access environment variables from your shell before any profile-specific values are applied. So, if you had export MY_APP_REPLICAS=5 in your shell and your values-dev.yaml was missing the replicas key, Skaffold would look for {{ .Values.replicas }} and, finding no explicit value, would then check environment variables. This behavior is crucial for understanding how values cascade and can sometimes lead to unexpected defaults if you’re not careful about what environment variables are set.
The underlying mechanism for rendering these templates, whether they are Helm charts, Kustomize overlays, or raw Kubernetes manifests with Go templating, is Skaffold’s render phase. This phase is executed before apply. Skaffold effectively generates the final YAML files and then passes those to kubectl apply or your Helm/Kustomize command.
This pre-application rendering is what allows Skaffold to provide features like hot-reloading and consistent deployments across different environments. It ensures that the exact configuration you intend to deploy is what actually gets applied.
The next step is understanding how to combine multiple templating mechanisms, like Helm and Kustomize, within a single Skaffold configuration.