Home / Documentation / Architecture Guides / Cloud Native Patterns

Cloud Native Patterns

13 min read
Updated Jun 19, 2025

Introduction

Cloud-native is an approach to building and running applications that exploits the advantages of the cloud computing delivery model. Cloud-native applications are designed from the ground up to thrive in a dynamic, distributed environment.

Definition: Cloud-native applications are built using microservices, packaged in containers, dynamically orchestrated, and actively managed to optimize resource utilization.

Key Characteristics

  • Designed as loosely coupled microservices
  • Packaged in lightweight containers
  • Dynamically managed by orchestration platforms
  • Developed with agile methodologies and DevOps practices
  • Continuously delivered using CI/CD pipelines

The Twelve-Factor App

The twelve-factor methodology is a set of best practices for building cloud-native applications:

I
Codebase
II
Dependencies
III
Config
IV
Backing Services
V
Build, Release, Run
VI
Processes
VII
Port Binding
VIII
Concurrency
IX
Disposability
X
Dev/Prod Parity
XI
Logs
XII
Admin Processes

Configuration Management

Environment-based Configuration javascript
// Bad: Hard-coded configuration
const db = mysql.createConnection({
  host: 'prod-db.example.com',
  user: 'admin',
  password: 'hardcoded-password'
});

// Good: Environment-based configuration
const db = mysql.createConnection({
  host: process.env.DB_HOST,
  user: process.env.DB_USER,
  password: process.env.DB_PASSWORD,
  port: process.env.DB_PORT || 3306,
  ssl: process.env.DB_SSL === 'true'
});

// Configuration validation
const requiredEnvVars = ['DB_HOST', 'DB_USER', 'DB_PASSWORD'];
const missing = requiredEnvVars.filter(v => !process.env[v]);

if (missing.length > 0) {
  throw new Error(`Missing required environment variables: ${missing.join(', ')}`);
}

Containerization Patterns

Containers are the foundation of cloud-native applications. Here are key patterns for containerization:

Multi-Stage Builds

Optimized Dockerfile dockerfile
# Stage 1: Build
FROM node:16-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build

# Stage 2: Runtime
FROM node:16-alpine
RUN apk add --no-cache tini
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nodejs -u 1001

WORKDIR /app
COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist
COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=nodejs:nodejs /app/package.json ./

USER nodejs
EXPOSE 3000

ENTRYPOINT ["/sbin/tini", "--"]
CMD ["node", "dist/server.js"]

Container Patterns

Sidecar Pattern

Deploy components of an application into separate containers to provide isolation and encapsulation.

Use cases: Logging, Monitoring, Proxy

Ambassador Pattern

Create helper containers that proxy network connections from the main container to external services.

Use cases: Service mesh, API gateway

Adapter Pattern

Standardize and normalize application output for consumption by external monitoring systems.

Use cases: Log formatting, Metrics export

Orchestration Patterns

Container orchestration platforms like Kubernetes provide patterns for managing containerized applications:

Deployment Strategies

Rolling Update:
┌──────┐ ┌──────┐ ┌──────┐     ┌──────┐ ┌──────┐ ┌──────┐
│ v1.0 │ │ v1.0 │ │ v1.0 │ →   │ v2.0 │ │ v1.0 │ │ v1.0 │ → ...
└──────┘ └──────┘ └──────┘     └──────┘ └──────┘ └──────┘

Blue-Green Deployment:
┌───────────────────┐    ┌───────────────────┐
│   Blue (v1.0)     │    │   Green (v2.0)    │
│   [Active]        │ →  │   [Active]        │
└───────────────────┘    └───────────────────┘

Canary Deployment:
┌───────────────────┐    ┌───────────────────┐
│  v1.0 (90%)       │    │  v2.0 (50%)       │
│  v2.0 (10%)       │ →  │  v1.0 (50%)       │ → 100% v2.0
└───────────────────┘    └───────────────────┘

Kubernetes Patterns

StatefulSet for Stateful Applications yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres-cluster
spec:
  serviceName: postgres-service
  replicas: 3
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
      - name: postgres
        image: postgres:14
        ports:
        - containerPort: 5432
        env:
        - name: POSTGRES_REPLICATION_MODE
          value: master
        - name: POSTGRES_REPLICATION_USER
          value: replicator
        volumeMounts:
        - name: postgres-storage
          mountPath: /var/lib/postgresql/data
  volumeClaimTemplates:
  - metadata:
      name: postgres-storage
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: fast-ssd
      resources:
        requests:
          storage: 100Gi

Serverless Patterns

Serverless computing allows you to build applications without managing infrastructure:

Event-Driven Architecture

AWS Lambda Function javascript
// Image processing Lambda function
exports.handler = async (event) => {
    const s3 = new AWS.S3();
    
    // Process each S3 event
    for (const record of event.Records) {
        const bucket = record.s3.bucket.name;
        const key = decodeURIComponent(record.s3.object.key.replace(/\+/g, ' '));
        
        // Get the image from S3
        const image = await s3.getObject({
            Bucket: bucket,
            Key: key
        }).promise();
        
        // Process the image
        const processed = await processImage(image.Body);
        
        // Save processed images
        await Promise.all([
            // Thumbnail
            s3.putObject({
                Bucket: bucket,
                Key: `thumbnails/${key}`,
                Body: processed.thumbnail,
                ContentType: 'image/jpeg'
            }).promise(),
            
            // Optimized version
            s3.putObject({
                Bucket: bucket,
                Key: `optimized/${key}`,
                Body: processed.optimized,
                ContentType: 'image/jpeg'
            }).promise()
        ]);
        
        // Send notification
        await sns.publish({
            TopicArn: process.env.NOTIFICATION_TOPIC,
            Message: JSON.stringify({
                bucket,
                key,
                status: 'processed',
                timestamp: new Date().toISOString()
            })
        }).promise();
    }
    
    return {
        statusCode: 200,
        body: JSON.stringify({
            message: 'Images processed successfully',
            count: event.Records.length
        })
    };
};

Serverless Patterns

  • Function Composition: Chain functions together for complex workflows
  • Fan-out/Fan-in: Process items in parallel then aggregate results
  • Async Processing: Decouple long-running tasks using queues
  • Event Sourcing: Store all changes as events
  • CQRS: Separate read and write operations

Data Management Patterns

Cloud-native applications require special considerations for data management:

Database Per Service

Polyglot Persistence

services:
  user-service:
    database: PostgreSQL  # Relational data
    reason: "ACID compliance for user accounts"
    
  product-catalog:
    database: MongoDB     # Document store
    reason: "Flexible schema for product attributes"
    
  session-service:
    database: Redis       # Key-value store
    reason: "High-speed session management"
    
  analytics-service:
    database: ClickHouse  # Column store
    reason: "Time-series data and analytics"
    
  search-service:
    database: Elasticsearch  # Search engine
    reason: "Full-text search capabilities"

Event Streaming

Kafka Event Streaming javascript
// Producer
const { Kafka } = require('kafkajs');

const kafka = new Kafka({
  clientId: 'order-service',
  brokers: ['kafka-1:9092', 'kafka-2:9092', 'kafka-3:9092']
});

const producer = kafka.producer();

// Publish order events
async function publishOrderEvent(order) {
  await producer.send({
    topic: 'order-events',
    messages: [
      {
        key: order.id,
        value: JSON.stringify({
          eventType: 'OrderCreated',
          orderId: order.id,
          customerId: order.customerId,
          items: order.items,
          total: order.total,
          timestamp: new Date().toISOString()
        }),
        headers: {
          'correlation-id': generateCorrelationId(),
          'event-version': '1.0'
        }
      }
    ]
  });
}

// Consumer
const consumer = kafka.consumer({ groupId: 'inventory-service' });

await consumer.subscribe({ 
  topic: 'order-events', 
  fromBeginning: false 
});

await consumer.run({
  eachMessage: async ({ topic, partition, message }) => {
    const event = JSON.parse(message.value.toString());
    
    switch (event.eventType) {
      case 'OrderCreated':
        await updateInventory(event);
        break;
      case 'OrderCancelled':
        await restoreInventory(event);
        break;
    }
  },
});

Resilience Patterns

Build applications that gracefully handle failures and maintain availability:

Circuit Breaker

Prevent cascading failures by stopping requests to failing services.

class CircuitBreaker {
  constructor(options) {
    this.failureThreshold = options.failureThreshold || 5;
    this.timeout = options.timeout || 60000;
    this.state = 'CLOSED';
    this.failures = 0;
    this.nextAttempt = Date.now();
  }
  
  async call(fn) {
    if (this.state === 'OPEN') {
      if (Date.now() < this.nextAttempt) {
        throw new Error('Circuit breaker is OPEN');
      }
      this.state = 'HALF_OPEN';
    }
    
    try {
      const result = await fn();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }
}

Retry with Backoff

Automatically retry failed operations with exponential backoff.

async function retryWithBackoff(fn, options = {}) {
  const maxRetries = options.maxRetries || 3;
  const baseDelay = options.baseDelay || 1000;
  
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn();
    } catch (error) {
      if (i === maxRetries - 1) throw error;
      
      const delay = baseDelay * Math.pow(2, i);
      const jitter = Math.random() * delay * 0.1;
      
      await new Promise(resolve => 
        setTimeout(resolve, delay + jitter)
      );
    }
  }
}

Bulkhead

Isolate resources to prevent total system failure.

class BulkheadPool {
  constructor(size) {
    this.size = size;
    this.available = size;
    this.queue = [];
  }
  
  async acquire() {
    if (this.available > 0) {
      this.available--;
      return Promise.resolve();
    }
    
    return new Promise((resolve) => {
      this.queue.push(resolve);
    });
  }
  
  release() {
    if (this.queue.length > 0) {
      const resolve = this.queue.shift();
      resolve();
    } else {
      this.available++;
    }
  }
}

Security Patterns

Implement security best practices for cloud-native applications:

Zero Trust Security

  • Mutual TLS (mTLS) for service-to-service communication
  • Service mesh for identity and access management
  • Runtime security scanning
  • Secrets management with rotation
  • Policy as Code enforcement

Container Security

Security Scanning in CI/CD yaml
# GitLab CI security scanning
stages:
  - build
  - scan
  - deploy

container_scanning:
  stage: scan
  image: registry.gitlab.com/gitlab-org/security-products/analyzers/klar
  script:
    - /analyzer run
  artifacts:
    reports:
      container_scanning: gl-container-scanning-report.json
  only:
    - branches

sast:
  stage: scan
  image: registry.gitlab.com/gitlab-org/security-products/sast
  script:
    - /analyzer run
  artifacts:
    reports:
      sast: gl-sast-report.json

dependency_scanning:
  stage: scan
  image: registry.gitlab.com/gitlab-org/security-products/dependency-scanning
  script:
    - /analyzer run
  artifacts:
    reports:
      dependency_scanning: gl-dependency-scanning-report.json

Observability

Implement comprehensive observability for cloud-native applications:

Structured Logging

Structured Logging with Context javascript
const winston = require('winston');

// Configure structured logging
const logger = winston.createLogger({
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.errors({ stack: true }),
    winston.format.json()
  ),
  defaultMeta: {
    service: 'order-service',
    version: process.env.APP_VERSION,
    environment: process.env.NODE_ENV
  },
  transports: [
    new winston.transports.Console()
  ]
});

// Middleware to add request context
app.use((req, res, next) => {
  req.logger = logger.child({
    requestId: req.headers['x-request-id'] || uuid(),
    userId: req.user?.id,
    method: req.method,
    path: req.path,
    ip: req.ip
  });
  next();
});

// Usage in application
app.post('/orders', async (req, res) => {
  req.logger.info('Creating order', {
    customerId: req.body.customerId,
    itemCount: req.body.items.length,
    total: req.body.total
  });
  
  try {
    const order = await createOrder(req.body);
    
    req.logger.info('Order created successfully', {
      orderId: order.id,
      processingTime: Date.now() - req.startTime
    });
    
    res.json(order);
  } catch (error) {
    req.logger.error('Order creation failed', {
      error: error.message,
      stack: error.stack,
      customerId: req.body.customerId
    });
    
    res.status(500).json({ error: 'Failed to create order' });
  }
});

Metrics and Monitoring

Prometheus Metrics javascript
const prometheus = require('prom-client');

// Create metrics
const httpRequestDuration = new prometheus.Histogram({
  name: 'http_request_duration_seconds',
  help: 'Duration of HTTP requests in seconds',
  labelNames: ['method', 'route', 'status_code'],
  buckets: [0.1, 0.5, 1, 2, 5]
});

const ordersCreated = new prometheus.Counter({
  name: 'orders_created_total',
  help: 'Total number of orders created',
  labelNames: ['status']
});

const activeConnections = new prometheus.Gauge({
  name: 'active_connections',
  help: 'Number of active connections'
});

// Metrics middleware
app.use((req, res, next) => {
  const start = Date.now();
  
  res.on('finish', () => {
    const duration = (Date.now() - start) / 1000;
    httpRequestDuration
      .labels(req.method, req.route?.path || 'unknown', res.statusCode)
      .observe(duration);
  });
  
  next();
});

// Expose metrics endpoint
app.get('/metrics', (req, res) => {
  res.set('Content-Type', prometheus.register.contentType);
  res.end(prometheus.register.metrics());
});

Best Practices

Follow these best practices for successful cloud-native implementations:

Design Principles

  • Design for failure - assume everything will fail
  • Keep services stateless when possible
  • Use managed services to reduce operational overhead
  • Implement health checks and readiness probes
  • Version your APIs and maintain backward compatibility
  • Use feature flags for gradual rollouts

Operational Excellence

  • Automate everything - deployment, scaling, recovery
  • Implement comprehensive monitoring and alerting
  • Practice chaos engineering to test resilience
  • Use GitOps for infrastructure and application deployment
  • Implement proper secret management
  • Regular security scanning and updates
Remember: Cloud-native is not just about technology - it's about culture, processes, and continuous improvement.
Note: This documentation is provided for reference purposes only. It reflects general best practices and industry-aligned guidelines, and any examples, claims, or recommendations are intended as illustrative—not definitive or binding.