Self-Service Infrastructure
Self-service infrastructure empowers developers to provision and manage resources without depending on operations teams. This accelerates development cycles while maintaining security, compliance, and cost controls through automation and guardrails.
The Self-Service Model
Self-service infrastructure provides developers with controlled access to provision resources on demand.
Traditional Infrastructure Provisioning
Manual processes create bottlenecks:
sequenceDiagram
participant D as Developer
participant T as Ticket System
participant O as Ops Team
participant C as Cloud Provider
D->>T: Create infrastructure request ticket
Note over T: Wait in queue (2-3 days)
O->>T: Review ticket
O->>O: Manual validation
O->>C: Provision resources
C-->>O: Resources created
O->>T: Update ticket with details
T-->>D: Resources ready
Note over D,C: Total time: 3-7 days
Self-Service Approach
Automated provisioning with guardrails:
sequenceDiagram
participant D as Developer
participant P as Platform API
participant V as Validation
participant I as IaC Engine
participant C as Cloud Provider
D->>P: Request infrastructure via portal/CLI
P->>V: Validate request against policies
V-->>P: Approved
P->>I: Generate and apply IaC
I->>C: Provision resources
C-->>I: Resources created
I-->>P: Deployment complete
P-->>D: Resources ready with endpoints
Note over D,C: Total time: 2-5 minutes
Self-Service Patterns
Several patterns enable safe, scalable self-service infrastructure.
Template-Based Provisioning
Predefined templates ensure consistency:
# infrastructure-template.yaml
apiVersion: platform.example.com/v1
kind: InfrastructureTemplate
metadata:
name: web-service-standard
description: Standard web service with database and cache
spec:
parameters:
- name: serviceName
type: string
required: true
pattern: "^[a-z][a-z0-9-]*$"
- name: environment
type: string
enum: [dev, staging, production]
required: true
- name: instanceType
type: string
default: medium
enum: [small, medium, large]
- name: databaseType
type: string
default: postgres
enum: [postgres, mysql, mongodb]
resources:
- type: kubernetes.namespace
name: "${serviceName}-${environment}"
- type: database.instance
name: "${serviceName}-db"
engine: "${databaseType}"
size: "${instanceType}"
backup:
enabled: true
retention: 7
- type: cache.redis
name: "${serviceName}-cache"
size: small
- type: storage.s3bucket
name: "${serviceName}-assets"
encryption: true
lifecycle:
deleteAfter: 90
networking:
ingress:
enabled: true
host: "${serviceName}.${environment}.example.com"
tls: true
egress:
allowList:
- "*.example.com"
- "api.thirdparty.com"
monitoring:
- type: dashboard
metrics: [cpu, memory, requests, errors]
- type: alerts
critical: [high_error_rate, high_latency]
Using the template:
# CLI usage
platform deploy \
--template web-service-standard \
--param serviceName=payment-api \
--param environment=production \
--param instanceType=large \
--param databaseType=postgres
Policy-Based Guardrails
Enforce organizational requirements automatically:
# Policy enforcement engine
from enum import Enum
from typing import List, Dict
class PolicyLevel(Enum):
ERROR = "error" # Blocks deployment
WARNING = "warning" # Allows with warning
INFO = "info" # Informational only
class Policy:
def __init__(self, name: str, level: PolicyLevel):
self.name = name
self.level = level
def evaluate(self, resource: Dict) -> bool:
raise NotImplementedError
class CostLimitPolicy(Policy):
def __init__(self, max_monthly_cost: float):
super().__init__("cost-limit", PolicyLevel.ERROR)
self.max_monthly_cost = max_monthly_cost
def evaluate(self, resource: Dict) -> bool:
estimated_cost = self.calculate_cost(resource)
return estimated_cost <= self.max_monthly_cost
def calculate_cost(self, resource: Dict) -> float:
# Cost calculation logic
costs = {
'small': 50,
'medium': 150,
'large': 500
}
return costs.get(resource.get('instanceType'), 0)
class EncryptionPolicy(Policy):
def __init__(self):
super().__init__("encryption-required", PolicyLevel.ERROR)
def evaluate(self, resource: Dict) -> bool:
if resource.get('type') == 'database':
return resource.get('encryption', False) is True
if resource.get('type') == 'storage':
return resource.get('encryption', False) is True
return True
class TaggingPolicy(Policy):
def __init__(self, required_tags: List[str]):
super().__init__("required-tags", PolicyLevel.WARNING)
self.required_tags = required_tags
def evaluate(self, resource: Dict) -> bool:
tags = resource.get('tags', {})
return all(tag in tags for tag in self.required_tags)
class PolicyEngine:
def __init__(self):
self.policies = [
CostLimitPolicy(max_monthly_cost=1000),
EncryptionPolicy(),
TaggingPolicy(required_tags=['owner', 'cost-center', 'environment'])
]
def validate(self, resource: Dict) -> Dict:
results = {
'allowed': True,
'errors': [],
'warnings': []
}
for policy in self.policies:
if not policy.evaluate(resource):
if policy.level == PolicyLevel.ERROR:
results['allowed'] = False
results['errors'].append(f"Policy violation: {policy.name}")
elif policy.level == PolicyLevel.WARNING:
results['warnings'].append(f"Policy warning: {policy.name}")
return results
Policy enforcement in action:
graph TD
A[Infrastructure Request] --> B{Policy Validation}
B -->|Cost Check| C{Within Budget?}
C -->|No| D[REJECT: Cost Limit Exceeded]
C -->|Yes| E{Encryption Check}
E -->|Missing| F[REJECT: Encryption Required]
E -->|Present| G{Tagging Check}
G -->|Missing| H[WARN: Tags Required]
G -->|Complete| I[APPROVE]
H --> I
I --> J[Provision Infrastructure]
style D fill:#ffcccc
style F fill:#ffcccc
style H fill:#fff4cc
style I fill:#ccffcc
Resource Lifecycle Management
Automated cleanup prevents waste:
// Automatic resource lifecycle management
package platform
import (
"time"
)
type ResourceLifecycle struct {
CreatedAt time.Time
ExpiresAt *time.Time
LastAccessed time.Time
Owner string
Environment string
}
type LifecyclePolicy struct {
Environment string
MaxAge time.Duration
IdleTimeout time.Duration
NotifyBefore time.Duration
}
var defaultPolicies = []LifecyclePolicy{
{
Environment: "dev",
MaxAge: 30 * 24 * time.Hour, // 30 days
IdleTimeout: 7 * 24 * time.Hour, // 7 days
NotifyBefore: 3 * 24 * time.Hour, // 3 days
},
{
Environment: "staging",
MaxAge: 90 * 24 * time.Hour, // 90 days
IdleTimeout: 14 * 24 * time.Hour, // 14 days
NotifyBefore: 7 * 24 * time.Hour, // 7 days
},
{
Environment: "production",
MaxAge: 0, // No automatic deletion
IdleTimeout: 0,
NotifyBefore: 0,
},
}
func (lm *LifecycleManager) EvaluateResource(resource Resource) Action {
lifecycle := resource.Lifecycle
policy := lm.getPolicyForEnvironment(lifecycle.Environment)
now := time.Now()
// Check max age
if policy.MaxAge > 0 {
age := now.Sub(lifecycle.CreatedAt)
if age > policy.MaxAge {
return Action{
Type: ActionDelete,
Reason: "Resource exceeded maximum age",
When: now,
}
}
// Notify before deletion
timeUntilExpiry := policy.MaxAge - age
if timeUntilExpiry < policy.NotifyBefore {
return Action{
Type: ActionNotify,
Reason: "Resource will expire soon",
When: now,
}
}
}
// Check idle timeout
if policy.IdleTimeout > 0 {
idleTime := now.Sub(lifecycle.LastAccessed)
if idleTime > policy.IdleTimeout {
return Action{
Type: ActionDelete,
Reason: "Resource has been idle too long",
When: now,
}
}
}
return Action{Type: ActionNone}
}
Self-Service Infrastructure Components
Implementing self-service requires several integrated systems.
API Layer
RESTful API for infrastructure requests:
// Infrastructure API
import express from 'express';
import { validateRequest, enforceQuota, auditLog } from './middleware';
const app = express();
interface InfrastructureRequest {
template: string;
parameters: Record<string, any>;
requester: string;
team: string;
}
app.post('/api/v1/infrastructure',
validateRequest,
enforceQuota,
auditLog,
async (req, res) => {
const request: InfrastructureRequest = req.body;
try {
// 1. Validate against policies
const validation = await policyEngine.validate(request);
if (!validation.allowed) {
return res.status(403).json({
error: 'Policy validation failed',
details: validation.errors
});
}
// 2. Check quota
const quota = await quotaService.checkQuota(request.team);
if (!quota.available) {
return res.status(429).json({
error: 'Quota exceeded',
details: quota
});
}
// 3. Generate infrastructure code
const infraCode = await templateEngine.render(
request.template,
request.parameters
);
// 4. Submit for provisioning
const deployment = await provisioningEngine.deploy({
code: infraCode,
requester: request.requester,
team: request.team
});
// 5. Register in catalog
await catalog.register({
id: deployment.id,
owner: request.team,
resources: deployment.resources
});
res.status(202).json({
deploymentId: deployment.id,
status: 'pending',
estimatedTime: '2-5 minutes',
trackingUrl: `/api/v1/deployments/${deployment.id}`
});
} catch (error) {
res.status(500).json({
error: 'Infrastructure provisioning failed',
message: error.message
});
}
}
);
app.get('/api/v1/deployments/:id', async (req, res) => {
const deployment = await provisioningEngine.getStatus(req.params.id);
res.json(deployment);
});
Quota Management
Prevent runaway resource consumption:
# Quota system
from dataclasses import dataclass
from typing import Dict
@dataclass
class Quota:
team: str
max_cpu_cores: int
max_memory_gb: int
max_storage_gb: int
max_databases: int
max_cost_monthly: float
@dataclass
class Usage:
cpu_cores: int
memory_gb: int
storage_gb: int
databases: int
cost_monthly: float
class QuotaService:
def __init__(self):
self.quotas: Dict[str, Quota] = {}
self.usage: Dict[str, Usage] = {}
def check_quota(self, team: str, requested: Usage) -> Dict:
"""Check if team has quota for requested resources"""
quota = self.quotas.get(team)
current = self.usage.get(team, Usage(0, 0, 0, 0, 0.0))
# Calculate new usage
new_usage = Usage(
cpu_cores=current.cpu_cores + requested.cpu_cores,
memory_gb=current.memory_gb + requested.memory_gb,
storage_gb=current.storage_gb + requested.storage_gb,
databases=current.databases + requested.databases,
cost_monthly=current.cost_monthly + requested.cost_monthly
)
# Check each limit
violations = []
if new_usage.cpu_cores > quota.max_cpu_cores:
violations.append(f"CPU limit: {new_usage.cpu_cores}/{quota.max_cpu_cores}")
if new_usage.memory_gb > quota.max_memory_gb:
violations.append(f"Memory limit: {new_usage.memory_gb}/{quota.max_memory_gb}")
if new_usage.storage_gb > quota.max_storage_gb:
violations.append(f"Storage limit: {new_usage.storage_gb}/{quota.max_storage_gb}")
if new_usage.databases > quota.max_databases:
violations.append(f"Database limit: {new_usage.databases}/{quota.max_databases}")
if new_usage.cost_monthly > quota.max_cost_monthly:
violations.append(f"Cost limit: ${new_usage.cost_monthly}/${quota.max_cost_monthly}")
return {
'available': len(violations) == 0,
'violations': violations,
'current_usage': current,
'requested': requested,
'quota': quota
}
Audit Trail
Track all infrastructure changes:
// Audit logging
package audit
import (
"encoding/json"
"time"
)
type AuditEvent struct {
ID string `json:"id"`
Timestamp time.Time `json:"timestamp"`
Action string `json:"action"`
Resource string `json:"resource"`
Requester string `json:"requester"`
Team string `json:"team"`
Parameters map[string]interface{} `json:"parameters"`
Result string `json:"result"`
IPAddress string `json:"ip_address"`
UserAgent string `json:"user_agent"`
}
type AuditLogger interface {
Log(event AuditEvent) error
Query(filters map[string]string) ([]AuditEvent, error)
}
type DatabaseAuditLogger struct {
db Database
}
func (l *DatabaseAuditLogger) Log(event AuditEvent) error {
query := `
INSERT INTO audit_log (
id, timestamp, action, resource,
requester, team, parameters, result,
ip_address, user_agent
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`
parametersJSON, _ := json.Marshal(event.Parameters)
return l.db.Exec(query,
event.ID,
event.Timestamp,
event.Action,
event.Resource,
event.Requester,
event.Team,
parametersJSON,
event.Result,
event.IPAddress,
event.UserAgent,
)
}
Self-Service Best Practices
Successful self-service infrastructure follows key principles.
Progressive Permissions
Start restrictive, expand based on trust:
# Permission tiers
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: platform-tier-1 # New teams
rules:
- apiGroups: ["platform.example.com"]
resources: ["infrastructuretemplates"]
verbs: ["get", "list"]
resourceNames: ["web-service-small", "worker-service-small"]
- apiGroups: ["platform.example.com"]
resources: ["infrastructuredeployments"]
verbs: ["create", "get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: platform-tier-2 # Established teams
rules:
- apiGroups: ["platform.example.com"]
resources: ["infrastructuretemplates"]
verbs: ["get", "list"]
# Access to more templates
- apiGroups: ["platform.example.com"]
resources: ["infrastructuredeployments"]
verbs: ["create", "get", "list", "update", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: platform-tier-3 # Trusted teams
rules:
- apiGroups: ["platform.example.com"]
resources: ["*"]
verbs: ["*"]
Blast Radius Limitation
Isolate failures to prevent cascading issues:
Isolation Strategies:
├── Network Isolation
│ ├── Separate VPCs per team
│ ├── Restricted security groups
│ └── Network policies
├── Resource Isolation
│ ├── Separate namespaces
│ ├── Resource quotas
│ └── Pod security policies
├── Data Isolation
│ ├── Separate database instances
│ ├── Encrypted at rest
│ └── Access controls
└── Blast Radius Controls
├── Rate limiting
├── Circuit breakers
└── Automatic rollback
Key Takeaways
- Self-service infrastructure reduces dependency on operations teams by enabling developers to provision resources through automated, policy-driven systems
- Template-based provisioning ensures consistency and best practices while allowing customization within safe boundaries
- Policy guardrails enforce security, cost, and compliance requirements automatically without manual reviews
- Quota management prevents resource exhaustion by setting team-level limits on compute, storage, and costs
- Audit trails provide accountability and compliance evidence for all infrastructure changes
- Progressive permission models allow teams to earn more capabilities as they demonstrate responsible platform usage
- Successful self-service balances developer autonomy with organizational governance through automation and clear boundaries