opscape

Text processing

envsubst

Replace $VAR and ${VAR} references in text with the values of environment variables.

envsubst [OPTION...] [SHELL-FORMAT]

8 of 8 examples run in Debian 13.7 on 2026-10-31 · manual: envsubst(1)

envsubst reads text on standard input, replaces every $NAME and ${NAME} with the value of that environment variable, and writes the result to standard output. It is the lightweight way to fill in a configuration template from variables, common in container entrypoints and deploy scripts, without the quoting hazards of eval or a here-document.

It does exactly one thing. It does not understand defaults like ${PORT:-8080}, command substitution or arithmetic; it only sees variables that are exported, and by default it replaces every variable reference, including ones you meant to keep, such as nginx's $uri. Pass a list of variable names (the SHELL-FORMAT argument) to restrict it.

It comes from GNU gettext (Debian package gettext-base), so it is not installed on every minimal system.

Examples

Substitute variables in a line of text

echo 'Hello $USER, your home is ${HOME}.' | envsubst
explain
output
Hello dev, your home is /home/dev.

The single quotes keep the shell from expanding the variables, so envsubst receives them literally and fills them in from the environment. Both $NAME and ${NAME} forms work.

Render a config file from a template

app.conf.tpl
server {
    listen ${PORT};
    server_name ${DOMAIN};
    root /srv/${APP}/public;
}
export PORT=8080 DOMAIN=demo.example APP=demo
envsubst < app.conf.tpl > app.conf
cat app.conf
output
server {
    listen 8080;
    server_name demo.example;
    root /srv/demo/public;
}

The template's three variables are replaced with the exported values and the result is written to a new file. Keep templates and rendered files separate so you can re-render.

Unexported variables are not seen

PORT=9090
echo 'port=$PORT' | envsubst
PORT=9090 envsubst <<< 'port=$PORT'
output
port=
port=9090

A plain shell variable is not in the environment of child processes, so envsubst replaces $PORT with an empty string. Setting it in front of the command (or with export) passes it in.

Unknown variables become empty

echo 'user=$DB_USER pass=$DB_PASSWORD' | DB_USER=app envsubst
explain
output
user=app pass=

DB_PASSWORD is not set, and envsubst silently substitutes nothing. There is no error and no way to set a default; check required variables in your script before rendering.

Substitute only selected variables

site.tpl
location / {
    proxy_pass http://127.0.0.1:${PORT};
    try_files $uri $uri/ =404;
}
export PORT=8080
envsubst '$PORT' < site.tpl
output
location / {
    proxy_pass http://127.0.0.1:8080;
    try_files $uri $uri/ =404;
}

The argument lists the variables to replace (written with $, in single quotes). nginx's own $uri is left alone. Without the list, $uri would be replaced by an empty string and the config would break.

List the variables a template uses

app.conf.tpl
server {
    listen ${PORT};
    server_name ${DOMAIN};
    root /srv/${APP}/public;
}
envsubst --variables "$(cat app.conf.tpl)"
explain
output
PORT
DOMAIN
APP

--variables (-v) prints the names referenced in a SHELL-FORMAT string, one per line, without substituting anything. Passing the whole template as the format is a quick way to see what a template needs.

Fill a template from a .env file

set -a; . ./.env; set +a
echo 'api=${API_URL} debug=${DEBUG}' | envsubst
output
api=http://localhost:8080 debug=false

set -a exports every variable assigned while it is on, so sourcing .env puts its values into the environment. This only works for .env files that are valid shell syntax.

Shell default syntax is not supported

echo 'port=${PORT:-8080}' | envsubst
explain
output
port=${PORT:-8080}

envsubst only recognizes plain $NAME and ${NAME}. The :- form is not a reference to it, so the text is passed through unchanged. Set defaults in the shell before calling it: export PORT=${PORT:-8080}.

Common pitfalls

  • Double-quoting the input (echo "$HOME" | envsubst) lets the shell substitute first, so envsubst has nothing to do. Use single quotes or a file.

  • Every $word in the input is a candidate, including $1-like text in scripts or $uri in nginx configs. Pass an explicit variable list.

  • Missing variables become empty strings with no warning, which can produce a config that parses but is wrong. Validate required variables first, for example with : "${PORT:?PORT is not set}".

  • Do not render into the template file itself (envsubst < f > f): the shell truncates it before envsubst reads it.

GNU vs BSD and macOS

The examples above ran the GNU/Linux version. These are the differences that matter on macOS and the BSDs (written from their documentation, not run here).

  • envsubst is part of GNU gettext and behaves the same everywhere it is installed. macOS does not include it by default; it comes with the gettext package from Homebrew or MacPorts.

Options worth knowing

A selection, described in our own words. For the full list, see envsubst(1) or envsubst --help.

OptionWhat it does
-v, --variablesPrint the variable names in SHELL-FORMAT instead of substituting.
--helpShow usage and exit.
-V, --versionShow version information and exit.

Related