How to use environment variables in a Kubernetes manifest

If you’re a Docker/Compose user new to Kubernetes, you might have noticed that you can’t use environment variables in your manifest files. This might not be a huge deal to most, especially since there are other (better) ways of managing environment variables in Kubernetes, but to someone getting started it can be a pain.
For example, say we have an Nginx container that we’re migrating from Docker Compose to Kubernetes. We want to change this container’s port number depending on whether we’re running it in production or in development. Our docker-compose.yml looks like this:

version: '3.7'

services:
  nginx:
    build: images/nginx
    hostname: nginx
    restart: always
    ports:
      - "${NGINX_PORT:-443}:443"

Here, ${NGINX_PORT:-443} will use the environment variable NGINX_PORT if it’s set, and if not, it’ll default to 443. As a convenience, if we set this environment variable using an .env file, Docker Compose will automatically read this file when it runs. This makes it easy for us to make two different .env files – one for each environment – and easily swap them out whenever we call docker-compose.

Unfortunately, if we migrate our container over to Kubernetes, this system no longer works. We’d need to use some other method of storing configuration metadata, like a ConfigMap. But let’s say you’re in a rush, and you just want to get the container working, best practices be damned. In that case, there is a way to use environment variables in Kubernetes manifests, and that’s with envsubst.

envsubst

envsubst is a command-line program that substitutes environment variables into standard input, then spits out a copy with the variables replaced. This is perfect for Kubernetes, as it gives us an exact copy of our original manifest only with any environment variables replaced. This way, we can keep our .env file until we have a more Kubernetes-friendly way of managing these variables and secrets.

First, we need to source our .env file to load the variables into our environment:

source. env

Next, we run envsubst, hand it our manifest file, then pipe the output to kubectl. Our manifest file is called nginx.yml and looks like this:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        ports:
        - containerPort: 443
        - hostPort: ${NGINX_PORT:-443}:443

If we handed this to kubectl, it would complain about the hostPort not being set. But if we pass it off to envsubst, we get this as the output:

$ envsubst < nginx.yml
...
ports:
- containerPort: 443
- hostPort: 443:443

There we go, a fully formatted manifest file with our environment variable set. Now, we can pass this directly to kubectl:

$ envsubst < nginx.yml | kubectl apply -f -

And our Nginx container gets deployed! If we want to change our .env file, we just run source <new env file> and re-run envsubst. If you want to make this even easier, I’ve created a script you can use. You can call this script just like you would kubectl. This script sources any .env file in the current directory. If you’re running kubectl apply or kubectl delete, it calls envsubst to swap out your environment variables, then it passes the code on to the real kubectl. Save this code to a text file, mark it as executable, and run it just like you would kubectl:

#!/bin/bash
ENV_FILE=.env

source $ENV_FILE

if [[ "$1" == "apply" ]] || [[ "$1" == "delete" ]]; then
    envsubst < $3 | kubectl $1 $2 -
else
    kubectl "$@"
fi

Again, this isn’t a best practice, and I wouldn’t recommend using this method in production. But if you’re coming from Docker Compose, you’re still learning Kubernetes, and you just want to get up and running, I hope this helps!

Leave a Reply