17.3.2 Environment Separation
A focused guide to Environment Separation, connecting core concepts with practical Docker and container operations.
Environment separation, at the infrastructure level, means ensuring development, staging, and production environments run on genuinely separate hosts, networks, and credential systems, distinct from the configuration-file organization that determines which values apply to which environment, since even perfectly externalized configuration provides no protection if the underlying infrastructure itself is shared.
Why configuration separation alone is insufficient
Externalizing configuration correctly, so that a single image can run against any environment's specific values, solves the problem of which configuration applies where; it does not by itself prevent a container running with development configuration from sharing the same host, network, or credential store as a container running with production configuration, which is a distinct and equally important concern:
docker run --network shared-net -e DATABASE_URL=postgres://dev-db:5432/app my-api
docker run --network shared-net -e DATABASE_URL=postgres://prod-db:5432/app my-api
Both containers sharing the same network here means a misconfigured or compromised development container has a direct network path to whatever else is reachable on that shared network, including, in this example, anything that might be running with production configuration alongside it, entirely independent of whether each container's own configuration was correctly externalized.
Separate hosts or clusters per environment
The strongest, clearest form of environment separation runs each environment on genuinely distinct underlying infrastructure, separate hosts, separate cloud accounts, or separate orchestrator clusters, so that a problem in one environment has no direct infrastructure-level path to affect another:
dev-cluster.example.com
staging-cluster.example.com
production-cluster.example.com
This is the most robust approach but also the most resource-intensive; for smaller projects, a less complete but still meaningful separation, distinct networks and distinct credential systems on shared underlying hardware, provides much of the same benefit at lower cost, though with a correspondingly smaller, but non-zero, residual risk from sharing the underlying host or cluster itself.
Network-level separation as a minimum baseline
At minimum, even when environments share underlying physical or cloud infrastructure, they should never share a network, ensuring no direct network path exists between a development container and a production one regardless of what each one's own configuration happens to say:
docker network create dev-network
docker network create production-network
A development container should have no network route to anything on the production network, and vice versa, which removes an entire class of risk, a development container accidentally or maliciously reaching a production database, for instance, that configuration externalization alone does nothing to prevent.
Separate credential systems, not just separate credential values
Beyond simply using different specific credential values per environment, the actual systems issuing and managing those credentials, a secrets manager instance, an identity provider, a certificate authority, should themselves be genuinely separate per environment where the stakes warrant it, since a compromise of a shared credential management system would affect every environment relying on it simultaneously:
vault-dev.example.com
vault-production.example.com
This is a meaningfully stronger guarantee than simply storing different secret values within the same shared secrets manager instance, since a vulnerability or compromise affecting the secrets manager itself would, in the shared-instance scenario, potentially expose every environment's credentials simultaneously rather than being contained to just one.
Separate registries or registry namespaces
Container images for different environments, or at minimum the credentials used to push and pull them, should be similarly separated where the organization's risk tolerance warrants it, since a compromised development-environment registry credential should not, ideally, also grant access to push or pull production images:
docker login registry-dev.example.com
docker login registry-production.example.com
For smaller organizations, a single registry with carefully scoped, separate access credentials per environment provides a reasonable middle ground between full registry separation and no separation at all.
Avoiding shared, long-lived development data in production-adjacent infrastructure
A common, subtle violation of environment separation is a development or staging database that, over time, accumulates a copy of production data, copied in once for realistic testing and never refreshed or properly isolated afterward, which means a less-protected environment now holds genuinely sensitive, production-equivalent data:
pg_dump production_db | psql staging_db
If production data genuinely needs to exist in a lower environment for realistic testing, it should be anonymized as part of that copy process, and the lower environment should still be held to access controls appropriate for the sensitivity of the data it now actually contains, rather than the more relaxed controls typically applied to a genuinely synthetic, low-stakes development environment.
Verifying separation directly rather than assuming it
Periodically confirming that environments are genuinely isolated, no unexpected network route, no shared credential system, no informally copied production data sitting in a lower environment, through a direct, deliberate audit catches drift that accumulates gradually and often goes unnoticed until an incident specifically reveals it:
docker network inspect production-network --format '{{.Containers}}'
Confirming this list contains only genuinely production containers, with nothing from a development or staging context having somehow been connected to it over time, is a concrete, periodic check worth performing rather than assuming separation remains intact indefinitely once initially established.
Common mistakes
- Assuming correctly externalized configuration alone provides adequate environment separation, without also ensuring genuinely separate underlying infrastructure, networks, and credential systems.
- Sharing a single network across environments, allowing a direct network path between a development container and a production one regardless of each one's own configuration.
- Using a single, shared secrets manager or identity provider instance across every environment, concentrating the impact of any compromise across all of them simultaneously.
- Copying production data into a lower environment without anonymization, and without correspondingly tightening that lower environment's access controls to match the sensitivity of the data it now contains.
- Never auditing actual environment separation directly, assuming it remains intact indefinitely based only on how it was originally configured.
Environment separation requires genuine infrastructure-level isolation, separate hosts or clusters where feasible, separate networks as a minimum baseline, and separate credential systems for anything where the stakes warrant it, since configuration externalization alone determines which values an image uses but provides no protection against a shared underlying infrastructure connecting environments that should remain genuinely, structurally isolated from each other.