Skip to content

YAML Configuration — Complete Implementation Guide

DodaTech Updated 2026-06-28 7 min read

In this tutorial, you will learn about YAML Configuration. We cover key concepts, practical examples, and best practices to help you master this topic.

YAML configuration is the preferred format for complex application settings due to its readability, support for comments, anchors, aliases, and nested structures that mirror application configuration objects.

What You'll Learn

By the end of this tutorial, you will know YAML syntax for configuration, use anchors to reduce duplication, substitute environment variables, and validate YAML configuration with schemas.

Why It Matters

YAML is the industry standard for configuration in cloud-native applications. Kubernetes, Docker Compose, CI/CD pipelines, and Spring Boot all use YAML. Mastering YAML configuration is essential for backend development.

Real-World Use

DodaTech's entire infrastructure is defined in YAML: Kubernetes manifests, Docker Compose files, CI/CD pipelines, and application configuration files all use YAML.

YAML Configuration Learning Path

flowchart LR
  A[Configuration Files] --> B[YAML Configuration]
  B --> C[Syntax]
  B --> D[Anchors]
  B --> E[ENV Substitution]
  B --> F{You Are Here}
  style F fill:#f90,color:#fff

YAML Syntax Overview

YAML uses indentation-based structure with key-value pairs.

# Basic types
string: "hello"           # String
integer: 42               # Number
float: 3.14               # Float
boolean: true             # Boolean
null_value: null          # Null
date: 2026-06-28          # ISO Date

# Collections
list:
  - item1
  - item2
  - item3

dictionary:
  key1: value1
  key2: value2

# Multiline strings
description: |
  This is a block
  of text that preserves
  newlines.

summary: >
  This is a folded block
  that joins lines
  into a single paragraph.
const yaml = require("js-yaml");
const config = yaml.load(`
server:
  port: 3000
  host: 0.0.0.0
  cors:
    origins:
      - http://localhost:5173
      - https://app.example.com
`);

console.log("Server port:", config.server.port);
console.log("CORS origins:", config.server.cors.origins.join(", "));
// Server port: 3000
// CORS origins: http://localhost:5173, https://app.example.com

Anchors and Aliases

YAML anchors reduce duplication by referencing shared configuration blocks.

# config.yaml
database-defaults: &database-defaults
  engine: postgresql
  port: 5432
  pool:
    min: 2
    max: 10
  timeout: 5000

development:
  database:
    <<: *database-defaults
    host: localhost
    name: myapp_dev

staging:
  database:
    <<: *database-defaults
    host: staging-db.internal
    name: myapp_staging
    pool:
      max: 15  # Override specific values

production:
  database:
    <<: *database-defaults
    host: prod-db.amazonaws.com
    name: myapp_production
    pool:
      max: 50
    timeout: 10000
const yaml = require("js-yaml");
const fs = require("fs");
const config = yaml.load(fs.readFileSync("./config.yaml", "utf8"));

console.log("Development DB host:", config.development.database.host);
console.log("Production DB pool max:", config.production.database.pool.max);
// Development DB host: localhost
// Production DB pool max: 50

Environment Variable Substitution

Inject environment variables into YAML configuration at load time.

# config.yaml with placeholders
server:
  port: ${PORT:-3000}
  host: ${HOST:-0.0.0.0}

database:
  host: ${DB_HOST}
  port: ${DB_PORT:-5432}
  name: ${DB_NAME}
  user: ${DB_USER}
  password: ${DB_PASSWORD}

logging:
  level: ${LOG_LEVEL:-info}
function loadYAMLWithEnv(filePath) {
  const fs = require("fs");
  const yaml = require("js-yaml");

  let content = fs.readFileSync(filePath, "utf8");

  content = content.replace(/\${([^:-]+)(?::-([^}]+))?}/g, (_, name, defaultVal) => {
    return process.env[name] || defaultVal || "";
  });

  const config = yaml.load(content);
  console.log("Loaded YAML with env substitution from", filePath);
  return config;
}

// With DB_HOST=prod.example.com and DB_NAME=mydb
const config = loadYAMLWithEnv("./config.yaml");
console.log("DB host:", config.database.host);
// DB host: prod.example.com

Multi-Document YAML

YAML supports multiple documents in one file, separated by ---.

# config.yaml
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  port: "3000"

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app
spec:
  replicas: 3
  template:
    spec:
      containers:
        - name: app
          image: myapp:latest
const yaml = require("js-yaml");
const fs = require("fs");

const documents = yaml.loadAll(fs.readFileSync("./config.yaml", "utf8"));
console.log(`Loaded ${documents.length} YAML documents`);

documents.forEach((doc, i) => {
  console.log(`Document ${i + 1}: ${doc.kind} - ${doc.metadata.name}`);
});
// Loaded 2 YAML documents
// Document 1: ConfigMap - app-config
// Document 2: Deployment - app

YAML Validation with JSON Schema

Validate YAML configuration against a schema at startup.

const yaml = require("js-yaml");
const fs = require("fs");

function validateConfig(config, schema) {
  const errors = [];

  function validate(obj, schema, path = "") {
    for (const [key, rules] of Object.entries(schema)) {
      const value = obj[key];
      const fullPath = path ? `${path}.${key}` : key;

      if (rules.required && (value === undefined || value === null)) {
        errors.push(`Missing required: ${fullPath}`);
        continue;
      }

      if (value === undefined || value === null) continue;

      if (rules.type && typeof value !== rules.type) {
        errors.push(`Type error: ${fullPath} should be ${rules.type}, got ${typeof value}`);
      }

      if (rules.properties && typeof value === "object") {
        validate(value, rules.properties, fullPath);
      }

      if (rules.min !== undefined && value < rules.min) {
        errors.push(`Min error: ${fullPath} should be >= ${rules.min}`);
      }
    }
  }

  validate(config, schema);
  return { valid: errors.length === 0, errors };
}

const schema = {
  server: {
    required: true,
    type: "object",
    properties: {
      port: { required: true, type: "number", min: 1024 },
      host: { required: true, type: "string" }
    }
  },
  database: {
    required: true,
    type: "object",
    properties: {
      host: { required: true, type: "string" },
      name: { required: true, type: "string" }
    }
  }
};

const config = yaml.load(fs.readFileSync("./config.yaml", "utf8"));
const result = validateConfig(config, schema);
console.log("Config valid:", result.valid);
if (!result.valid) result.errors.forEach(e => console.log("  -", e));

Common Mistakes

  1. Using tabs instead of spaces -- YAML uses spaces for indentation. Tabs cause parse errors. Configure your editor to use spaces for .yaml files.

  2. Not quoting strings with special characters -- Strings containing colons, #, or brackets need quotes. Use quotes for all strings when in doubt.

  3. Overusing anchors -- Anchors reduce duplication but make configuration harder to read and debug. Use them for simple value reuse, not complex logic.

  4. Loading untrusted YAML -- yaml.load() can execute arbitrary code with some YAML libraries. Use yaml.safeLoad() or configure the loader to allow only safe types.

  5. Not validating configuration at startup -- Invalid YAML or missing values cause runtime errors. Validate the loaded configuration against a schema immediately.

Practice Questions

  1. What is the difference between | and > in YAML multiline strings? | (literal) preserves newlines. > (folded) replaces newlines with spaces. | is better for configuration values.

  2. How do YAML anchors reduce duplication? Anchors (&name) define a block, aliases (*name) reference it. Merge keys (<<:) allow overriding specific values from the anchor.

  3. Why should you validate YAML configuration at startup? To catch invalid values, missing required fields, and type mismatches before the application starts serving traffic.

  4. Challenge: Implement a YAML configuration loader that supports inheritance from multiple base configurations.

class YAMLConfigInheritance {
  static loadWithInheritance(filePath) {
    const yaml = require("js-yaml");
    const fs = require("fs");
    const config = yaml.load(fs.readFileSync(filePath, "utf8"));

    if (config.extends) {
      const bases = Array.isArray(config.extends) ? config.extends : [config.extends];
      let merged = {};

      bases.forEach(base => {
        const basePath = require("path").join(require("path").dirname(filePath), base);
        const baseConfig = yaml.load(fs.readFileSync(basePath, "utf8"));
        merged = { ...merged, ...baseConfig };
      });

      return { ...merged, ...config, extends: undefined };
    }

    return config;
  }
}

FAQ

What is the recommended file extension for YAML?

.yaml is preferred. .yml is also supported but .yaml is the official extension.

Can YAML configuration be used for secrets?

No. YAML config files should not contain secrets. Use environment variables or secrets manager references.

How do I comment out a block in YAML?

Use # at the start of each line. YAML doesn't support block comments. Most editors have a comment toggle.

What is the maximum nesting depth in YAML?

YAML doesn't specify a limit, but most parsers handle up to 100 levels. Practical configs should stay under 5-6 levels.

Should I use YAML anchors or environment variables for shared values?

Use anchors for values shared within a config file. Use environment variables for values that change between deployments.

Mini Project

Build a YAML configuration system that supports anchors for common database configurations, environment variable substitution, multi-document loading, and JSON Schema validation at startup.

class YAMLConfigSystem {
  constructor(configDir = "./config") {
    this.configDir = configDir;
    this.schema = null;
  }

  setSchema(schema) {
    this.schema = schema;
  }

  load(env = process.env.NODE_ENV || "development") {
    const path = require("path");
    const fs = require("fs");
    const yaml = require("js-yaml");

    let content = fs.readFileSync(path.join(this.configDir, `${env}.yaml`), "utf8");
    content = this.substituteEnv(content);
    const config = yaml.load(content);

    if (this.schema) {
      const errors = this.validate(config);
      if (errors.length > 0) {
        throw new Error(`Config validation failed:\n${errors.join("\n")}`);
      }
    }

    return config;
  }

  substituteEnv(content) {
    return content.replace(/\$\{([^}]+)\}/g, (_, key) => {
      return process.env[key] || "";
    });
  }

  validate(config, schema = this.schema, path = "") {
    const errors = [];
    for (const [key, rules] of Object.entries(schema)) {
      const val = config[key];
      if (rules.required && (val === undefined || val === null)) {
        errors.push(`${path}.${key} is required`);
      }
    }
    return errors;
  }
}

const system = new YAMLConfigSystem();
system.load("production");
console.log("YAML configuration system ready");

What's Next

Now that you understand YAML configuration, learn about TOML configuration. Then explore configuration hierarchy.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro