Setting Up Config Server (Consul/etcd) for Microservices
Centralized configuration management allows changing service settings without rebuilding images or restarting. Database URLs, feature flags, timeouts, API keys—all stored in one place, versioned, and updated dynamically.
Tools Overview
| Tool | Strengths |
|---|---|
| Consul KV | Service Discovery integration, health checks |
| etcd | Basis for Kubernetes, high reliability |
| Vault | Secrets + configuration, encryption |
| Spring Cloud Config | Configuration from Git repository |
| AWS Parameter Store | Managed, IAM integration |
Consul KV Store
import Consul from 'consul';
const consul = new Consul({ host: process.env.CONSUL_HOST });
class ConfigService {
private cache = new Map<string, string>();
async get(key: string): Promise<string | null> {
const result = await consul.kv.get(`config/order-service/${key}`);
return result?.Value ? Buffer.from(result.Value, 'base64').toString() : null;
}
async getAll(prefix: string): Promise<Record<string, string>> {
const results = await consul.kv.get({
key: `config/order-service/${prefix}`,
recurse: true
});
return results?.reduce((acc, item) => {
const shortKey = item.Key.replace(`config/order-service/${prefix}/`, '');
acc[shortKey] = Buffer.from(item.Value, 'base64').toString();
return acc;
}, {}) ?? {};
}
// Watch — dynamic updates without restart
watch(key: string, callback: (value: string) => void): void {
const watcher = consul.watch({
method: consul.kv.get,
options: { key: `config/order-service/${key}` }
});
watcher.on('change', (data) => {
if (data?.Value) {
const value = Buffer.from(data.Value, 'base64').toString();
this.cache.set(key, value);
callback(value);
}
});
}
}
// Usage
const config = new ConfigService();
// Feature flags with hot reload
let enableNewCheckout = false;
config.watch('features/new-checkout', (value) => {
enableNewCheckout = value === 'true';
logger.info(`Feature new-checkout: ${enableNewCheckout}`);
});
etcd
import { Etcd3 } from 'etcd3';
const etcd = new Etcd3({ hosts: process.env.ETCD_HOSTS.split(',') });
// Write configuration
await etcd.put('config/payment-service/timeout').value('5000');
await etcd.put('config/payment-service/retries').value('3');
// Read with namespace
const namespace = etcd.namespace('config/payment-service/');
const timeout = await namespace.get('timeout').number();
const retries = await namespace.get('retries').number();
// Watch for changes
const watcher = await namespace.watch().key('timeout').create();
watcher.on('put', (res) => {
console.log('timeout changed to:', res.value.toString());
});
Spring Cloud Config
Git repository as configuration source:
# config-server application.yml
spring:
cloud:
config:
server:
git:
uri: https://github.com/company/config-repo
search-paths: '{application}'
clone-on-start: true
Client (in each service):
# bootstrap.yml
spring:
application:
name: order-service
config:
import: "configserver:http://config-server:8888"
profiles:
active: production
On startup, the service loads order-service/production.yml from the Git repository.
HashiCorp Vault for Secrets
import vault from 'node-vault';
const client = vault({
apiVersion: 'v1',
endpoint: process.env.VAULT_ADDR,
token: process.env.VAULT_TOKEN // in K8s — via ServiceAccount
});
// Read secret
const secret = await client.read('secret/data/order-service/production');
const dbPassword = secret.data.data.db_password;
const stripeKey = secret.data.data.stripe_secret_key;
// Dynamic secrets — Vault generates temporary credentials for PostgreSQL
const dbCreds = await client.read('database/creds/order-service-role');
// dbCreds.data.username, dbCreds.data.password — unique, expire after TTL
Kubernetes ConfigMap and Secret
Native solution for K8s:
apiVersion: v1
kind: ConfigMap
metadata:
name: order-service-config
data:
PAYMENT_TIMEOUT_MS: "5000"
MAX_RETRIES: "3"
FEATURE_NEW_CHECKOUT: "false"
---
apiVersion: v1
kind: Secret
metadata:
name: order-service-secrets
type: Opaque
stringData:
DATABASE_PASSWORD: "super-secret"
STRIPE_SECRET_KEY: "sk_live_..."
# Deployment
envFrom:
- configMapRef:
name: order-service-config
- secretRef:
name: order-service-secrets
ConfigMap limitation—no hot reload without pod recreation. For dynamic settings—Consul/etcd + watch or External Secrets Operator with Vault.
Implementation Timeline
- Consul KV with hot reload for feature flags — 2–3 days
- Vault for secrets + Kubernetes External Secrets — 3–5 days
- Spring Cloud Config with Git repository — 2–3 days







