Application Design Philosophy
The Docker platform is more than a set of tools; it is an opinionated workflow that encourages a specific architectural philosophy. Adopting this philosophy is essential for leveraging the full benefits of containers, resulting in applications that are more scalable, resilient, and reliable.
The core principles are:
Immutability: Treat infrastructure components (containers, hosts) as unchangeable.
Statelessness: Design applications to store no critical data inside the container.
Externalized State: Manage all persistent data in attached, reliable backing services.
1. Immutable Infrastructure
The Docker workflow promotes a shift from traditional, mutable infrastructure to immutable infrastructure.
Traditional (Mutable): Servers are launched and then configured, patched, and updated in place over their lifespan. This leads to configuration drift, making it difficult to guarantee that any two servers (e.g., staging and production) are identical.
Immutable (Docker's Way): Components are not modified after deployment. If an update is required (a security patch, a new code release), a new, updated image is built. The old containers are then destroyed, and new containers are deployed from the new image.
Atomic (Throwaway) Containers
This immutable approach leads to the concept of atomic or throwaway containers. The container is a hermetically-sealed, self-contained unit that is never "patched" or modified while running.
Benefits of this approach:
Consistency and Reliability: The exact same artifact (the image) is used in every environment (development, testing, production), eliminating "works on my machine" problems.
Atomic Deployments: During deployment, the entire running environment of the old application is thrown away. This prevents applications from accidentally relying on artifacts left by a previous release (e.g., old log files, temporary data).
Simplified Rollbacks: To roll back a change, you simply destroy the new containers and deploy containers from the previous, known-good image tag.
Horizontal Scalability: When you need to scale, you do not configure new servers; you simply launch more identical copies of the same image.
This philosophy extends to the host OS with Atomic Hosts (e.g., Fedora CoreOS, Bottlerocket OS), which are minimal operating systems designed for hosting containers and receiving atomic, image-based OS upgrades.
2. Stateless Applications: The Ideal
For containers to be truly immutable and throwaway, the application running inside them should be stateless.
A stateless application is one that does not store or track any critical information between requests. It treats each request as a self-contained transaction. Any data that needs to persist (like user sessions, application data, or user uploads) is stored in an external backing service, such as a database, cache, or object store.
This design is the ideal for a microservices architecture because:
Robustness: Instances can be started, stopped, or destroyed with little to no impact on the overall application's uptime.
Scalability: New copies of the container can be added or removed dynamically to match demand, without any complex state-transfer logic.
Portability: The container can be scheduled to run on any host in the cluster, as it has no dependencies on the local host's filesystem.
3. Handling State
Not all applications can be purely stateless. The key is to externalize any state, separating the ephemeral application logic (in the container) from the persistent data.
Configuration State (Environment Variables)
The most basic form of state is application configuration (e.g., database URLs, API keys, environment settings).
Bad Practice: Baking configuration files directly into the image. This limits the image's portability, as a different image would be required for staging and production.
Best Practice: Pass configuration into the container at runtime using environment variables.
Using environment variables allows the exact same image to be promoted across all environments, with only the external configuration changing. This also makes the configuration easily observable for debugging.
Persistent Application Data (Externalization)
For applications that must store persistent data (like databases or file uploads), that data must be stored outside the container. The container's local, writable filesystem is ephemeral and all data written to it will be lost when the container is destroyed.
There are two primary methods for externalizing state:
Attached Backing Services: This is the preferred model for cloud-native applications. Treat all backing stores (databases, caches, message queues) as attached resources. The application container is given the connection details (via environment variables) and accesses the service over the network.
File Storage: Centralized object stores like Amazon S3 or OpenStack Swift.
Data Storage: External managed databases like Amazon RDS, or a database running in its own container with its state externalized.
Docker Volumes: A Volume is the officially-supported mechanism for persisting data generated by a container. A Volume is a directory on the Docker host that is managed by Docker and mounted into the container.
Key Feature: The Volume's lifecycle is independent of the container. When a container is deleted, its Volume is not deleted. A new container can then be created and attached to the same Volume, regaining access to the data.
Use Case: This is the correct way to run a stateful application, such as a database (e.g., PostgreSQL or Redis), inside a Docker container. The database container is ephemeral, but its data files persist in the Volume.
Discouraged Practice: Binding to an arbitrary host path (e.g.,
-v /path/on/host:/path/in/container). This is strongly discouraged as it creates a hard dependency on a specific host's directory structure, breaking portability and preventing the container from being scheduled on other hosts in a cluster. Volumes abstract this away, allowing Docker to manage the storage location.
