Environment Konfiguration 12 Factor
12-Factor App: Konfiguration richtig gemacht
Die 12-Factor App Methodik definiert Best Practices für moderne Anwendungen. Faktor 3 "Config" ist dabei besonders wichtig.
Die 12 Faktoren (Übersicht)
| # | Faktor | Kurz |
|---|---|---|
| 1 | Codebase | Ein Repo, viele Deployments |
| 2 | Dependencies | Explizit deklarieren |
| 3 | Config | In Umgebungsvariablen |
| 4 | Backing Services | Als Ressourcen behandeln |
| 5 | Build, Release, Run | Strikt getrennt |
| 6 | Processes | Stateless |
| 7 | Port Binding | Services via Port exportieren |
| 8 | Concurrency | Horizontal skalieren |
| 9 | Disposability | Schnell starten/stoppen |
| 10 | Dev/Prod Parity | Umgebungen gleich halten |
| 11 | Logs | Als Event-Streams |
| 12 | Admin Processes | Als einmalige Prozesse |
Faktor 3: Config
❌ Niemals:
- Credentials im Code
- Config-Dateien ins Git
- Umgebungs-Checks im Code (if prod...)
✅ Immer:
- Umgebungsvariablen für alle Config
- Defaults für Development
- Validation beim Start
Config-Beispiel: Node.js
// config.js
const requiredEnvVars = [
'DATABASE_URL',
'REDIS_URL',
'JWT_SECRET'
];
// Validierung beim Start
for (const envVar of requiredEnvVars) {
if (!process.env[envVar]) {
console.error(`Missing required environment variable: ${envVar}`);
process.exit(1);
}
}
module.exports = {
// Server
port: parseInt(process.env.PORT) || 3000,
nodeEnv: process.env.NODE_ENV || 'development',
// Database
databaseUrl: process.env.DATABASE_URL,
// Redis
redisUrl: process.env.REDIS_URL,
// Auth
jwtSecret: process.env.JWT_SECRET,
jwtExpiresIn: process.env.JWT_EXPIRES_IN || '1h',
// Features
enableMetrics: process.env.ENABLE_METRICS === 'true',
logLevel: process.env.LOG_LEVEL || 'info',
// External Services
emailApiKey: process.env.EMAIL_API_KEY,
stripeSecretKey: process.env.STRIPE_SECRET_KEY,
};
Config-Beispiel: PHP
<?php
// config.php
class Config {
private static array $config;
public static function init(): void {
$required = ['DATABASE_URL', 'APP_SECRET'];
foreach ($required as $var) {
if (!getenv($var)) {
throw new RuntimeException("Missing: {$var}");
}
}
self::$config = [
'app' => [
'env' => getenv('APP_ENV') ?: 'development',
'debug' => getenv('APP_DEBUG') === 'true',
'secret' => getenv('APP_SECRET'),
],
'database' => [
'url' => getenv('DATABASE_URL'),
],
'mail' => [
'host' => getenv('MAIL_HOST') ?: 'localhost',
'port' => (int)(getenv('MAIL_PORT') ?: 587),
],
];
}
public static function get(string $key, $default = null) {
$keys = explode('.', $key);
$value = self::$config;
foreach ($keys as $k) {
if (!isset($value[$k])) return $default;
$value = $value[$k];
}
return $value;
}
}
// Usage
Config::init();
$debug = Config::get('app.debug', false);
Umgebungs-spezifische .env Dateien
# Struktur project/ ├── .env.example # Template (im Git) ├── .env # Lokal (nicht im Git!) ├── .env.test # Test-Umgebung └── .gitignore # Enthält .env # .env.example DATABASE_URL=postgres://user:pass@localhost:5432/mydb REDIS_URL=redis://localhost:6379 JWT_SECRET=change-me-in-production LOG_LEVEL=debug
Docker Compose Konfiguration
# docker-compose.yml
services:
app:
build: .
env_file:
- .env
environment:
- NODE_ENV=production
- LOG_LEVEL=${LOG_LEVEL:-info}
# Oder für verschiedene Umgebungen
# docker-compose.override.yml (Development)
services:
app:
env_file:
- .env.development
environment:
- NODE_ENV=development
Kubernetes ConfigMaps & Secrets
# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
LOG_LEVEL: "info"
ENABLE_METRICS: "true"
# secret.yaml (base64 encoded)
apiVersion: v1
kind: Secret
metadata:
name: app-secrets
type: Opaque
data:
DATABASE_URL: cG9zdGdyZXM6Ly91c2VyOnBhc3NAaG9zdDo1NDMyL2Ri
JWT_SECRET: c3VwZXItc2VjcmV0LWtleQ==
# deployment.yaml
spec:
containers:
- name: app
envFrom:
- configMapRef:
name: app-config
- secretRef:
name: app-secrets
Validierungs-Libraries
// Node.js: envalid
const envalid = require('envalid');
const env = envalid.cleanEnv(process.env, {
NODE_ENV: envalid.str({ choices: ['development', 'test', 'production'] }),
PORT: envalid.port({ default: 3000 }),
DATABASE_URL: envalid.url(),
JWT_SECRET: envalid.str({ minLength: 32 }),
ENABLE_CACHE: envalid.bool({ default: true }),
LOG_LEVEL: envalid.str({ default: 'info', choices: ['debug', 'info', 'warn', 'error'] }),
});
// Typisiert und validiert!
console.log(env.PORT); // number
console.log(env.isDev); // boolean (automatisch)
Secrets Management
# HashiCorp Vault vault kv put secret/myapp db_password=supersecret # AWS Secrets Manager aws secretsmanager get-secret-value --secret-id myapp/production # Doppler (SaaS) doppler run -- node app.js # 1Password CLI op run --env-file=.env -- node app.js
💡 Tipp:
Nutzen Sie den Enjyn Status Monitor um zu überwachen, ob Ihre Anwendungen korrekt konfiguriert starten.