Breaking systems into separately runnable or deployable units (web apps, microservices, databases)
The core purpose of the Container Diagram (Level 2) is to perform a technical decomposition of your software system — breaking it down from a single opaque box (as seen in the System Context) into its major separately runnable or deployable units.
This decomposition is not arbitrary. It reflects real architectural and operational boundaries: units that can be built, tested, deployed, scaled, monitored, secured, and (in many cases) replaced or retired independently. These are the containers in the C4 Model.
What Makes Something a Container?
A container is:
- A distinct, cohesive piece of software that executes code or hosts data.
- Separately runnable or deployable — meaning it can be started, stopped, restarted, scaled, or moved without necessarily affecting other containers.
- Has a clear runtime boundary — it runs in its own process, container image, serverless execution environment, or database instance.
This definition deliberately aligns with how modern systems are actually built and operated — whether monoliths, microservices, serverless, or hybrid architectures.
Common Types of Containers You Will Identify
Here are the most frequent categories you’ll encounter when decomposing almost any real-world system:
- Web Applications / Backends
- Traditional server-side apps (Spring Boot, .NET Core, Django/Flask, Node.js Express)
- Single-page application (SPA) frontends when hosted separately (React, Angular, Vue served from their own domain/server)
- API-only services / REST/GraphQL backends
- Example: “Customer API”, “Admin Web Portal”, “Public Website”
- Mobile Applications
- Native iOS/Android apps
- Cross-platform apps (Flutter, React Native)
- Treated as containers because they run independently on user devices and communicate via APIs
- Example: “iOS Banking App”, “Android Field Service App”
- Databases & Data Stores
- Relational databases (PostgreSQL, MySQL, SQL Server, Oracle)
- NoSQL/document stores (MongoDB, DynamoDB, Cassandra)
- Caches (Redis, Memcached)
- Search engines (Elasticsearch, OpenSearch)
- File/blob storage (S3, Azure Blob, MinIO)
- Example: “PostgreSQL – Customer Data”, “Redis – Session Cache”
- Message Brokers & Event Streams
- Queues (RabbitMQ, ActiveMQ, AWS SQS)
- Event streaming platforms (Kafka, Amazon Kinesis, Azure Event Hubs)
- Example: “Kafka – Order Events”, “RabbitMQ – Notifications”
- Serverless Functions / Batch Jobs
- Individual functions or groups of related functions (AWS Lambda, Azure Functions, Google Cloud Functions)
- Scheduled/cron jobs, ETL processes, background workers
- Example: “Payment Processing Lambda”, “Daily Report Generator”
- API Gateways / Reverse Proxies / Ingress
- Kong, Apigee, AWS API Gateway, NGINX, Traefik
- Often act as containers when they perform routing, rate limiting, authentication
- Example: “API Gateway”
- Third-Party / External SaaS Services (treated as black-box containers)
- When they form a distinct integration point with their own identity and deployment boundary
- Example: “Stripe – Payment Processing”, “SendGrid – Email Delivery”, “Auth0 – Identity Provider”
How to Perform the Decomposition (Step-by-Step)
- Start from the System Context Take your single “Software System” box and ask: “What major runnable/deployable pieces live inside this boundary?”
- Follow the data and control flow
- Where does user input arrive? → Likely a web/mobile frontend or API gateway
- Where is business logic executed? → Backend services
- Where is data persisted? → Databases
- Where are events published/consumed? → Message brokers
- Where are background tasks run? → Workers/functions
- Apply the “independence test” Could this piece be:
- Deployed to production independently?
- Scaled separately (e.g., more instances of the API than the database)?
- Developed by a different team?
- Replaced with a different technology without rewriting everything? If yes → strong candidate for its own container.
- Group logically when needed
- A monolithic backend that can’t be split yet → one container (“Monolith Backend”)
- A set of tightly coupled microservices that always deploy together → group as one container until boundaries become clear
- Name containers clearly and consistently
- Use noun phrases that describe responsibility + technology when helpful
- Examples: “Web Application (React SPA)”, “Order Service (Spring Boot)”, “PostgreSQL – Orders”, “Kafka – Event Bus”
Key Outcomes of This Decomposition
By breaking the system into containers, your Container Diagram will reveal:
- The technical architecture style (monolith, microservices, serverless, hybrid)
- Communication patterns (synchronous HTTP, asynchronous messaging, direct DB access)
- Technology diversity and potential complexity
- Deployment and scaling realities
- Risk hotspots (e.g., single shared database, heavy reliance on one broker)
This level of decomposition is where most architecture conversations happen in practice — because it shows how the system is really built and runs without drowning in code-level details.
In the hands-on section ahead, you’ll take a real or example System Context and systematically decompose it into containers using C4-PlantUML Studio — identifying boundaries, assigning responsibilities, choosing technologies, and drawing the relationships that make the system function.
Get ready to slice that big box into meaningful, deployable pieces — this is where the architecture starts to feel real and actionable.