A cloud-native application is not simply an application that runs in the cloud: it is an application designed to take advantage of the cloud, that is, conceived from the start to scale horizontally, tolerate failures, be deployed frequently and automatically, and run in dynamic environments (containers, orchestrators, managed platforms). To achieve this, the community has consolidated a series of proven patterns and principles that avoid reinventing the wheel and the typical mistakes. This lesson covers the most important ones: the 12-factor methodology, the sidecar and ambassador container patterns, the strangler fig migration pattern, auto-scaling, and externalized configuration. Mastering them lets you design resilient, portable, and operable systems, which are exactly the qualities the cloud rewards.
Contents
- The 12-factor methodology (summary).
- Sidecar pattern.
- Ambassador pattern.
- Strangler Fig pattern.
- Auto-scaling (horizontal and vertical).
- Externalized configuration.
- Common mistakes and tips.
- Exercises and solutions.
- The 12-factor methodology
The Twelve-Factor App is a set of best practices for building portable, scalable SaaS applications. Here is a grouped summary:
| # | Factor | In one sentence |
|---|---|---|
| 1 | Codebase | A single versioned repository per application |
| 2 | Dependencies | Explicitly declared and isolated |
| 3 | Config | In the environment, never in the code |
| 4 | Backing services | Databases, queues, etc. as swappable attached resources |
| 5 | Build, release, run | Separate and strict stages |
| 6 | Processes | The app runs as stateless processes |
| 7 | Port binding | The service is exposed through a port |
| 8 | Concurrency | Scale via the process model (horizontally) |
| 9 | Disposability | Fast startup and graceful shutdown |
| 10 | Dev/prod parity | Development, testing, and production as similar as possible |
| 11 | Logs | Treated as event streams to standard output |
| 12 | Admin processes | One-off tasks as ephemeral processes |
The three that have the most impact on cloud-native architecture: (3) config in the environment (we will look at it in detail), (6) stateless processes (allows freely scaling and replacing instances), and (9) disposability (instances must be able to die and be born without drama, the basis of resilience and auto-scaling).
- Sidecar pattern
The sidecar pattern (literally, the "sidecar" of a motorcycle) consists of deploying an auxiliary container alongside the application's main container, in the same Pod, sharing lifecycle, network, and volumes. The sidecar adds cross-cutting functionality (logging, proxy, security, synchronization) without modifying the main application.
apiVersion: v1
kind: Pod
metadata:
name: app-with-sidecar
spec:
containers:
- name: application # main container
image: myapp:1.0
volumeMounts:
- name: logs
mountPath: /var/log/app
- name: log-shipper # sidecar: sends logs to a central system
image: fluentbit:latest
volumeMounts:
- name: logs
mountPath: /var/log/app # shares the same volume
volumes:
- name: logs
emptyDir: {} # volume shared between the twoExplanation: the Pod has two containers. application writes its logs to /var/log/app. The log-shipper sidecar (Fluent Bit) reads from that same shared volume (emptyDir) and sends them to a central system. The application knows nothing about how its logs are exported: that cross-cutting responsibility lives in the sidecar. It is the basis of service meshes like Istio, where a sidecar proxy manages all network communication.
- Ambassador pattern
The ambassador pattern is a special type of sidecar that acts as an outbound proxy: the application talks to the ambassador as if it were the remote service, and the ambassador takes care of the details of the external connection (retries, circuit breaker, TLS encryption, sharding, monitoring). This way, the application is simplified and the network logic is centralized.
graph LR
APP[Application] -->|localhost:6379| AMB[Ambassador / Proxy]
AMB -->|retries, TLS, routing| EXT[(Remote service: DB / Cache)]Explanation of the diagram: the application always connects to localhost (to the ambassador), ignoring where the remote service actually is. The ambassador resolves the actual connection and adds retries, encryption, and routing. Key difference from the generic sidecar: the sidecar usually adds functions that accompany the app (logging, metrics); the ambassador specializes in managing outbound connections to external services.
- Strangler Fig pattern
The strangler fig pattern (named after the plant that wraps around and replaces the tree that supports it) is the recommended strategy for migrating a legacy monolithic system to microservices incrementally, without rewriting everything at once (which would be extremely risky). The idea: you place a proxy/router in front of the monolith and gradually redirect functionalities, one by one, to new services, until the monolith is empty and is retired.
graph TB
USER[User] --> ROUTER[Router / Facade]
ROUTER -->|/payments new| MS[Payments Microservice]
ROUTER -->|rest, legacy| MONO[Legacy monolith]Explanation: the router receives all traffic. The "payments" functionality has already been extracted to a new microservice, so the router sends /payments to the microservice and everything else still goes to the monolith. Over time, more routes migrate to the new side until the monolith is empty. Advantage: you migrate with controlled risk, being able to roll back each step, and you deliver value continuously instead of in a risky "big bang" migration.
- Auto-scaling
Auto-scaling automatically adjusts capacity according to demand. There are two dimensions:
| Type | What it does | When to use it |
|---|---|---|
| Horizontal (scale out/in) | Adds or removes instances/replicas | Preferred in cloud-native; requires stateless apps |
| Vertical (scale up/down) | Gives more/less CPU and RAM to an instance | When you cannot scale horizontally |
In Kubernetes, the Horizontal Pod Autoscaler (HPA) adjusts the number of Pods according to metrics:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: myapp-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: myapp-deployment # which Deployment it scales
minReplicas: 2 # never fewer than 2
maxReplicas: 10 # never more than 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70 # target: 70% CPU usageExplanation: the HPA watches the myapp-deployment Deployment. It keeps between 2 and 10 replicas and its goal is for average CPU usage to hover around 70%. If CPU goes above that, it creates more Pods; if it drops, it reduces them (without going below 2). For this to work, the application must be stateless (factors 6 and 9 of the 12 factors): any replica must be able to serve any request.
- Externalized configuration
Factor 3 of the 12 factors says: configuration does not go in the code. Credentials, URLs, and parameters that change between environments (development, testing, production) must be injected from outside, usually as environment variables or mounted files. This way, the same immutable image is promoted across environments by changing only the configuration, and there are no hardcoded secrets in the repository.
In Kubernetes this is done with ConfigMap (non-sensitive configuration) and Secret (sensitive data):
apiVersion: v1
kind: ConfigMap
metadata:
name: myapp-config
data:
LOG_LEVEL: "info"
FEATURE_NEW_DASHBOARD: "true"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-deployment
spec:
template:
spec:
containers:
- name: myapp
image: myapp:1.0
envFrom:
- configMapRef:
name: myapp-config # injects all keys as env vars
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef: # secret, not plain text in the manifest
name: myapp-secret
key: db-passwordExplanation: the ConfigMap stores non-sensitive parameters (LOG_LEVEL, feature flags). In the Deployment, envFrom.configMapRef injects all its keys as container environment variables. The database password is obtained from a Secret via secretKeyRef, so it does not appear in plain text in the deployment manifest. The myapp:1.0 image does not change between environments; only the ConfigMap and the Secret change. Remember that Kubernetes Secrets are only base64-encoded by default: for truly sensitive data, a dedicated secrets manager is advisable, and the approach should be validated with the security team.
Common Mistakes and Tips
- Storing state on the instance. Sessions or files in local memory break horizontal scaling: use external storage (DB, cache, object storage). This is factor 6.
- Hardcoded configuration or secrets in the repository. Always externalize configuration. A secret pushed to Git is a security incident.
- Big bang migration. Rewriting a monolith from scratch usually ends badly. Use the strangler pattern to migrate incrementally and reversibly.
- Auto-scaling a stateful app. If the app is not stateless, adding replicas does not solve the load and can corrupt data.
- Overusing sidecars. Each sidecar consumes resources in the Pod; add them when they provide clear cross-cutting value, not because it is trendy.
- Not designing for disposability. Implement graceful shutdown (handle
SIGTERM, close connections) so that scaling and deployments do not cut requests off midway.
Exercises
- Why is the "stateless processes" principle (factor 6) a prerequisite for horizontal auto-scaling to work well?
- Your team must modernize a large insurance monolith without being able to stop the service. Describe how you would apply the strangler fig pattern in the first steps.
- Differentiate the sidecar pattern from the ambassador pattern with an example.
Solutions
- Because when scaling horizontally the traffic is distributed among many interchangeable replicas, and any request can land on any replica. If a replica stored local state (the user's session, in-memory data), the same user's subsequent requests could go to another replica that does not have that state, causing errors. With stateless processes, all replicas are equivalent and can be freely added, removed, or replaced.
- Initial steps: (1) place a router/facade in front of the monolith that receives all traffic without changing behavior; (2) choose a bounded, low-risk functionality (for example, querying a catalog) and extract it to a new service; (3) configure the router so that that route goes to the new service and the rest stays in the monolith; (4) verify, measure, and, if all goes well, repeat with the next functionality. Each step is reversible.
- Sidecar: an auxiliary container that accompanies the app, providing a cross-cutting function; for example, a log-shipper that collects and sends the application's logs. Ambassador: a specialized sidecar that acts as an outbound proxy to external services; for example, a proxy the app connects to via
localhostthat manages retries and TLS to a remote database.
Conclusion
You have gone through the patterns that define a cloud-native architecture: the discipline of the 12 factors (stateless, external configuration, disposability), sidecars and ambassadors to add capabilities without touching the app, the strangler fig to migrate monoliths safely, auto-scaling to adapt to demand, and externalized configuration with ConfigMaps and Secrets. All of them share one goal: resilient, portable, and operable applications. In the last lesson of the module we will see how to create and manage all this infrastructure in a reproducible and versioned way with Infrastructure as Code (IaC).
Application Architecture Course
Module 1: Fundamentals of Application Architecture
- What Is Application Architecture?
- The Role of the Software Architect
- Quality Attributes and Non-Functional Requirements
- Architectural Decisions and Trade-offs
- Architecture Documentation: Views and the C4 Model
Module 2: Design Principles and Tactics
- Coupling, Cohesion and Separation of Concerns
- SOLID Principles Applied to Architecture
- DRY, KISS, YAGNI and Other Design Principles
- Architectural Tactics for Quality Attributes
- Managing Technical Debt
Module 3: Architectural Styles and Patterns
- Monolithic Architecture
- Layered Architecture (N-Tier)
- Client-Server Architecture
- Hexagonal Architecture (Ports and Adapters)
- Clean and Onion Architecture
Module 4: Distributed Architectures and Microservices
- Introduction to Distributed Systems
- Microservices Architecture
- Service Decomposition and Bounded Contexts
- API Gateway, Service Discovery and Inter-Service Communication
- Resilience Patterns: Circuit Breaker, Retry and Bulkhead
- The CAP Theorem and Data Consistency
Module 5: Event-Driven Architectures and Messaging
- Fundamentals of Event-Driven Architecture
- Asynchronous Messaging: Queues and Brokers
- Event Patterns: Event Sourcing and CQRS
- Managing Distributed Transactions: The Saga Pattern
- Real-Time Data Streaming
Module 6: Domain-Driven Design (DDD)
- Core DDD Concepts
- Strategic Design: Bounded Contexts and Ubiquitous Language
- Tactical Design: Entities, Aggregates and Repositories
- Context Mapping
Module 7: Data and Persistence
- Persistence Strategies: SQL vs NoSQL
- Data Access Patterns: Repository, Unit of Work and DAO
- Database per Service and Distributed Data Management
- Caching and Invalidation Strategies
Module 8: Cloud Architecture and Deployment
- Cloud Computing Fundamentals (IaaS, PaaS, SaaS)
- Containers and Orchestration with Docker and Kubernetes
- Serverless Architecture
- Cloud-Native Design Patterns
- Infrastructure as Code (IaC)
Module 9: Quality, Security and Observability
- Scalability: Horizontal vs Vertical and Load Balancing
- High Availability and Fault Tolerance
- Security by Design and Authentication/Authorization
- Observability: Logging, Metrics and Tracing
- Performance and Load Testing
