DevOps-bootcamp · leksjon

Maler for konfigurasjoner med envsubst og heredoc-er

Generer runtime-konfigurasjon fra miljøvariabler ved hjelp av envsubst og heredoc-er med anførselstegn.

Leksjon 2 av 413 trinn

Maler for konfigurasjoner med envsubst og heredoc-er er en gratis leksjon i DevOps-bootcamp på CoddyKit. Dette er leksjon 2 av 4. Du kan lese valgfritt 3 leksjoner fra denne læringsstien gratis i sin helhet – deretter låser CoddyKit PRO opp alle leksjoner, samt praktisk øving med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt. Den er en del av læringsløpet i DevOps-bootcamp, og fremdriften din synkroniseres mellom nettet og CoddyKit-appen. Kurset i DevOps-bootcamp inneholder totalt 4 leksjoner.

Hvorfor runtime-konfigurasjonsmaler er viktige

I DevOps- og container-arbeidsflyter må konfigurasjonsfiler som nginx.conf, prometheus.yml og docker-compose.yml ofte endres mellom miljøer — staging, produksjon og DR. Hardkoding av verdier fører til avvik mellom miljøene og eksponerte hemmeligheter.

Løsningen er runtime-konfigurasjonsmaler: lever en mal med plassholdere, og sett deretter inn de faktiske verdiene ved oppstart fra miljøvariabler. Slik forblir imaget uforanderlig, og konfigurasjonen kan kontrolleres.

  • Ingen hemmeligheter bygges inn i imagene
  • Det samme artefaktet kan promoteres på tvers av miljøer
  • Konfigurasjonen genereres rett før prosessen starter

To komplementære verktøy gjør dette enkelt i Bash: envsubst og anførselstegnssatte heredoc-er.

envsubst: Konfigurasjonsgeneratoren på én linje

envsubst er et lite GNU-verktøy som leser fra standard inndata, erstatter plassholdere som $VARIABLE og ${VARIABLE} med verdiene fra det gjeldende miljøet, og skriver til standard utdata.

Det leveres med gettext-pakken og er tilgjengelig i praktisk talt alle Linux-distribusjoner og Docker-basisimages.

  • Fungerer med alle tekstformater: NGINX, YAML, TOML, JSON og INI
  • Evaluerer ikke shell-syntaks — erstatter bare variabelreferanser
  • Sikkert: Det kjører ikke kommandoer inne i malen
#!/usr/bin/env bash
# Install check (usually already present)
which envsubst || apt-get install -y gettext-base

# Minimal demo
export APP_PORT=8080
export APP_HOST=api.example.com

echo 'server { listen ${APP_PORT}; server_name ${APP_HOST}; }' | envsubst
# Output: server { listen 8080; server_name api.example.com; }

Selektiv variabelerstatning

Som standard erstatter envsubst alle forekomster av $VAR den finner. Dette kan ødelegge NGINX-variabler som $uri eller $host — de er ekte NGINX-direktiver, ikke miljøvariablene Deres.

Angi en eksplisitt liste over variabler som første argument for å begrense erstatningen til bare disse navnene:

envsubst '$VAR1 $VAR2'

Argumentet er en streng omsluttet av enkle anførselstegn (slik at skallet ikke utvider den), og inneholder variabelnavnene De vil erstatte, adskilt med mellomrom eller linjeskift.

#!/usr/bin/env bash
export APP_PORT=8080
export APP_HOST=api.example.com

# NGINX template contains both our vars AND nginx vars ($uri, $host)
TEMPLATE='server {
  listen ${APP_PORT};
  server_name ${APP_HOST};
  location / {
    proxy_set_header Host $host;
    proxy_pass http://backend$uri;
  }
}'

# Only substitute APP_PORT and APP_HOST — leave $host and $uri untouched
echo "$TEMPLATE" | envsubst '${APP_PORT} ${APP_HOST}'

Maldfiler på disk

For faktiske konfigurasjoner bør malen lagres som en fil (for eksempel nginx.conf.template) sammen med Dockerfile. Ved oppstart av containeren kjører De envsubst for å opprette den endelige konfigurasjonsfilen før daemonen startes.

Dette er det kanoniske mønsteret som brukes av det offisielle NGINX-Docker-imaget.

#!/usr/bin/env bash
# File: nginx.conf.template
# (In practice this lives on disk; we write it here for demo purposes)
cat > /tmp/nginx.conf.template << 'TMPL'
server {
    listen ${NGINX_PORT};
    server_name ${SERVER_NAME};
    root /var/www/${APP_ENV};

    location / {
        proxy_pass http://app:${APP_PORT};
    }
}
TMPL

export NGINX_PORT=80
export SERVER_NAME=myapp.example.com
export APP_ENV=production
export APP_PORT=3000

# Generate final config
envsubst '${NGINX_PORT} ${SERVER_NAME} ${APP_ENV} ${APP_PORT}' \
  < /tmp/nginx.conf.template \
  > /tmp/nginx.conf

cat /tmp/nginx.conf

Siterte heredocs: Inline-maler uten midlertidig fil

En sitert heredoc (ved bruk av << 'EOF' med enkelt anførselstegn rundt skilletegnet) hindrer skallet i å utvide variabler eller kjøre kommandosubstitusjoner inne i blokken. Innholdet behandles som en bokstavelig streng.

Dette gjør heredocs til den perfekte måten å skrive en mal direkte på og sende den rett til envsubst — uten behov for en mellomliggende fil.

  • << EOF (uten anførselstegn) — skallet utvider $VAR umiddelbart
  • << 'EOF' (med anførselstegn) — innholdet er bokstavelig; utvidelsen utsettes til envsubst
#!/usr/bin/env bash
export DB_HOST=postgres.internal
export DB_PORT=5432
export DB_NAME=myapp_prod

# Quoted heredoc: shell does NOT expand $DB_HOST etc. yet
envsubst << 'EOF'
[database]
host     = ${DB_HOST}
port     = ${DB_PORT}
dbname   = ${DB_NAME}
EOF
# Output uses actual env var values — expansion done by envsubst, not the shell

Kombinere heredocs med utdataomdirigering

Send en sitert heredoc gjennom envsubst og omdiriger resultatet til en fil i ett uttrykk. Dette er det ryddigste mønsteret for å generere konfigurasjonsfiler i et entrypoint-skript.

Bruk selektiv substitusjon ('${VAR1} ${VAR2}') når målformatet (Prometheus, NGINX og så videre) har sin egen $variable-syntaks som må beskyttes.

#!/usr/bin/env bash
# entrypoint.sh — Docker container entrypoint
set -euo pipefail

export PROM_PORT=${PROM_PORT:-9090}
export SCRAPE_INTERVAL=${SCRAPE_INTERVAL:-15s}
export TARGET_HOST=${TARGET_HOST:-localhost:8080}

envsubst '${PROM_PORT} ${SCRAPE_INTERVAL} ${TARGET_HOST}' << 'EOF' > /etc/prometheus/prometheus.yml
global:
  scrape_interval: ${SCRAPE_INTERVAL}
  evaluation_interval: ${SCRAPE_INTERVAL}

scrape_configs:
  - job_name: 'app'
    static_configs:
      - targets: ['${TARGET_HOST}']

EOF

echo "[entrypoint] Prometheus config written on port ${PROM_PORT}"
exec prometheus --config.file=/etc/prometheus/prometheus.yml --web.listen-address=":${PROM_PORT}"

Standardverdier og validering før substitusjon

Stol aldri på at alle nødvendige variabler er satt. Bruk parameterutvidelse i Bash til å angi standardverdier eller avslutte med en tydelig feilmelding:

  • ${VAR:-default} — bruk default hvis VAR ikke er satt eller er tom
  • ${VAR:?error message} — avbryt med en feil hvis VAR ikke er satt eller er tom

Sett disse før envsubst kalles, slik at malen alltid mottar en konkret verdi eller skriptet stopper tidlig med en nyttig melding.

#!/usr/bin/env bash
set -euo pipefail

# Required — abort if missing
: "${DATABASE_URL:?DATABASE_URL must be set}"
: "${SECRET_KEY:?SECRET_KEY must be set}"

# Optional with defaults
export APP_PORT=${APP_PORT:-8000}
export LOG_LEVEL=${LOG_LEVEL:-info}
export WORKERS=${WORKERS:-4}

envsubst '${DATABASE_URL} ${SECRET_KEY} ${APP_PORT} ${LOG_LEVEL} ${WORKERS}' \
  < /app/config/app.conf.template \
  > /app/config/app.conf

echo "[init] Config generated — port=${APP_PORT} workers=${WORKERS} log=${LOG_LEVEL}"

Generere konfigurasjoner med flere seksjoner ved hjelp av flere heredocs

For komplekse konfigurasjoner som er bygget opp av logiske seksjoner kan De generere hver seksjon separat og sette dem sammen, eller bruke én heredoc som omfatter hele filen. Begge fremgangsmåtene fungerer — velg ut fra hva som gir best lesbarhet.

Når seksjoner skal inkluderes betinget (for eksempel en TLS-blokk bare hvis en sertifikatbane er satt), er fremgangsmåten med flere heredocs og if-blokker ryddigere.

#!/usr/bin/env bash
set -euo pipefail

export APP_HOST=${APP_HOST:-localhost}
export APP_PORT=${APP_PORT:-8080}
export TLS_CERT=${TLS_CERT:-}
export TLS_KEY=${TLS_KEY:-}

CONFIG_FILE=/tmp/app.conf

# Base section
envsubst '${APP_HOST} ${APP_PORT}' << 'BASE' > "$CONFIG_FILE"
[server]
host = ${APP_HOST}
port = ${APP_PORT}
BASE

# Conditional TLS section — only appended when cert is provided
if [[ -n "$TLS_CERT" && -n "$TLS_KEY" ]]; then
  envsubst '${TLS_CERT} ${TLS_KEY}' << 'TLS' >> "$CONFIG_FILE"

[tls]
cert_file = ${TLS_CERT}
key_file  = ${TLS_KEY}
TLS
  echo "[init] TLS enabled"
else
  echo "[init] TLS disabled (no cert/key provided)"
fi

cat "$CONFIG_FILE"

Docker-entrypoint-mønster

Det anbefalte Docker-entrypoint-mønsteret bruker et skallskript (docker-entrypoint.sh) til å generere konfigurasjoner ved oppstart, og overlater deretter kontrollen til hovedprosessen med exec. Ved å bruke exec erstattes skallprosessen av daemonen, slik at signaler (SIGTERM, SIGINT) når daemonen direkte — noe som er avgjørende for en kontrollert avslutning.

Malfilene legges til i imaget ved bygging; verdier injiseres ved kjøring fra docker run -e eller Kubernetes env: / envFrom:.

#!/usr/bin/env bash
# docker-entrypoint.sh
set -euo pipefail

# Validate required env vars
for var in DATABASE_URL REDIS_URL SECRET_KEY; do
  : "${!var:?$var is required}"
done

export APP_PORT=${APP_PORT:-8000}
export WORKERS=${WORKERS:-$(nproc)}

echo "[entrypoint] Generating configuration..."
envsubst '${DATABASE_URL} ${REDIS_URL} ${SECRET_KEY} ${APP_PORT} ${WORKERS}' \
  < /app/config/settings.toml.template \
  > /app/config/settings.toml

echo "[entrypoint] Starting server on port ${APP_PORT} with ${WORKERS} workers"
exec gunicorn app:application \
  --bind "0.0.0.0:${APP_PORT}" \
  --workers "${WORKERS}"

Kubernetes ConfigMap- og envsubst-mønster

I Kubernetes injiseres miljøvariabler via env: eller envFrom: i Pod-spesifikasjonen. Containerens entrypoint kaller envsubst for å materialisere konfigurasjoner før prosessen starter — det er ikke nødvendig med en ConfigMap per miljø.

Dette holder miljøspesifikke verdier i Kubernetes Secrets og ConfigMaps (for ikke-sensitive data), mens konfigurasjonsmalen ligger i imaget. Ett image, mange miljøer.

  • Bygging: COPY nginx.conf.template /etc/nginx/templates/
  • Kjøring: entrypoint kjører envsubst og skriver /etc/nginx/nginx.conf
  • K8s injiserer: APP_PORT, BACKEND_HOST fra en Secret/ConfigMap

Feilsøke envsubst: Finne manglende eller uavklarte variabler

Når en generert konfigurasjon inneholder den bokstavelige teksten ${VAR} i stedet for en verdi, var variabelen enten ikke eksportert eller ikke tatt med i substitusjonslisten. Bruk disse teknikkene til feilsøking:

  • printenv | sort — vis alle eksporterte variabler
  • Sammenlign plassholderne i malen med de eksporterte variablene ved hjelp av grep
  • Kjør envsubst og bruk grep på utdataene for å finne gjenværende ${-mønstre
  • Bruk set -u i det kallende skriptet, slik at referanser til variabler som ikke er satt, avbryter Bash-koden umiddelbart
#!/usr/bin/env bash
set -euo pipefail

TEMPLATE=/tmp/app.conf.template
OUTPUT=/tmp/app.conf

# Write a demo template
cat > "$TEMPLATE" << 'EOF'
host=${DB_HOST}
port=${DB_PORT}
name=${DB_NAME}
EOF

export DB_HOST=db.internal
export DB_PORT=5432
# DB_NAME intentionally left unset

envsubst < "$TEMPLATE" > "$OUTPUT"

# Detect unresolved placeholders
if grep -qE '\$\{[A-Z_]+\}' "$OUTPUT"; then
  echo "ERROR: unresolved placeholders found:"
  grep -oE '\$\{[A-Z_]+\}' "$OUTPUT" | sort -u
  exit 1
fi

echo "Config OK:"
cat "$OUTPUT"

Kunnskapssjekk: Selektiv substitusjon med envsubst

Tenk på en NGINX-konfigurasjonsmal som inneholder både applikasjonsvariabelen ${APP_PORT} og den innebygde NGINX-variabelen $uri. De kjører følgende kommando:

envsubst < nginx.conf.template > nginx.conf

Hva blir resultatet?

Oppsummering: Konfigurasjonsmaler med envsubst og heredocs

Nå har De et produksjonsklart verktøysett for å generere konfigurasjoner ved kjøring i Bash:

  • envsubst erstatter ${VAR}-plassholdere i alle tekstfiler ved hjelp av det gjeldende miljøet — uten skripting eller spesiell escaping
  • Selektiv substitusjon (envsubst '${VAR1} ${VAR2}') beskytter innebygde variabler i NGINX, Prometheus og lignende verktøy mot utilsiktet erstatning
  • Siterte heredocs (<< 'EOF') utsetter skallutvidelsen, slik at malinnholdet når envsubst uendret — uten behov for midlertidige filer
  • Valider før substitusjon: bruk ${VAR:?message} for å avbryte ved manglende obligatoriske variabler og ${VAR:-default} for valgfrie variabler
  • Docker-entrypoint-mønsteret: generer konfigurasjoner ved oppstart av containeren, og bruk deretter exec for daemonen slik at signaler håndteres riktig
  • Feilsøk uavklarte plassholdere ved å søke etter gjenværende ${-mønstre i utdataene før prosessen starter

Disse mønstrene holder containerimagene uforanderlige, holder hemmeligheter ute av kildekontrollen og sørger for konsistente konfigurasjoner i alle miljøer.

Gratis å komme i gang

Lær deg DevOps-bootcamp med en AI-veileder – gratis

Skriv og kjør ekte kode i nettleseren, få umiddelbar hjelp fra en AI-veileder som er tilgjengelig døgnet rundt, og fortsett der du slapp – på nettet eller i appen.

Kurs
142
Leksjoner
568

Ofte stilte spørsmål

Er leksjonen «Maler for konfigurasjoner med envsubst og heredoc-er» gratis?

Ja – du kan lese valgfritt 3 av leksjonene i læringsstien DevOps-bootcamp, inkludert «Maler for konfigurasjoner med envsubst og heredoc-er», gratis i sin helhet her på nettet. Deretter låser CoddyKit PRO opp alle leksjoner, samt interaktiv øving med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt. Kurset i DevOps-bootcamp inneholder totalt 4 leksjoner.

Hva lærer jeg i «Maler for konfigurasjoner med envsubst og heredoc-er»?

Generer runtime-konfigurasjon fra miljøvariabler ved hjelp av envsubst og heredoc-er med anførselstegn. Du øver på DevOps-bootcamp med praktisk kode som du kjører direkte i nettleseren, mens en AI-veileder som er tilgjengelig døgnet rundt, svarer på spørsmålene dine mens du jobber deg gjennom leksjonen.

Trenger jeg erfaring for å begynne med DevOps-bootcamp?

Ingen tidligere erfaring er nødvendig. DevOps-bootcamp på CoddyKit er lagt opp for både nybegynnere og viderekomne, så De kan begynne her eller helt fra start og lære i Deres eget tempo. Dette er leksjon 2 av 4.

Hvor lang tid tar leksjonen «Maler for konfigurasjoner med envsubst og heredoc-er»?

De fleste CoddyKit-leksjoner tar omtrent 5–10 minutter. Hver leksjon er kort og interaktiv, slik at De gjør jevne fremskritt og kan fortsette akkurat der De slapp – både på nettet og i appen.

Kan jeg skrive og kjøre kode i denne DevOps-bootcamp-leksjonen?

Ja. Alle DevOps-bootcamp-leksjoner har en innebygd kodeeditor, slik at De kan skrive og kjøre ekte kode direkte i nettleseren og få umiddelbar tilbakemelding fra AI – uten lokal konfigurering.

Alle leksjonene i dette kurset

  1. Slanke Dockerfiles og shell-entrypoint-er
  2. Maler for konfigurasjoner med envsubst og heredoc-er
  3. Skripting av skyressurser med CLI og jq
  4. Helsetester, readiness-sperrer og venteløkker
← Tilbake til DevOps-bootcamp