YAML Configuration — Complete Implementation Guide
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
Using tabs instead of spaces -- YAML uses spaces for indentation. Tabs cause parse errors. Configure your editor to use spaces for .yaml files.
Not quoting strings with special characters -- Strings containing colons, #, or brackets need quotes. Use quotes for all strings when in doubt.
Overusing anchors -- Anchors reduce duplication but make configuration harder to read and debug. Use them for simple value reuse, not complex logic.
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.
Not validating configuration at startup -- Invalid YAML or missing values cause runtime errors. Validate the loaded configuration against a schema immediately.
Practice Questions
What is the difference between | and > in YAML multiline strings? | (literal) preserves newlines. > (folded) replaces newlines with spaces. | is better for configuration values.
How do YAML anchors reduce duplication? Anchors (&name) define a block, aliases (*name) reference it. Merge keys (<<:) allow overriding specific values from the anchor.
Why should you validate YAML configuration at startup? To catch invalid values, missing required fields, and type mismatches before the application starts serving traffic.
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
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