Docker Compose production: where configuration breaks
Docker Compose conveniently describes the environment of a single machine as long as we are talking about a staging environment. In production, overrides, copies on different servers, and edits during incidents are added to the same file — that is how configuration drift begins. A tested build starts in production with a different set of volumes, limits, and addresses.
A separate problem is environment variables. Passwords, database and external service addresses are often baked into the image at build time instead of being supplied to the container at startup.
Then the same image carries staging settings: the production container reaches out to the test database, and this is discovered from logs, not during configuration validation.
Release rollback breaks for the same reason. Returning to the previous image does not return the settings — the file remains current, so old code starts with new variables and volumes and pulls along foreign values.
As long as configuration is not versioned together with the artifact, rollback cannot be considered a safe operation.
Environments also diverge over small things: project name, network, mount points, restart policy, startup order of dependent services. Each such difference is a separate class of failures, from a full disk to a service that started before the database. Branching by environment name inside a single file does not remove the problem, but hides it.
This is treated by separation: a common file with service descriptions, separate files for staging and production, all values only from environment variables, configuration validation before deployment, settings tied to the same artifact as the code.
Then rollback returns both the image and the configuration in full. We'll break down how to build this setup for you.
What separate environment configuration provides
Configuration baked into the image breaks the very idea of promoting a single artifact: to change the database address or connection limit, you have to rebuild the image, and therefore go through the entire validation path again.
Differences between the test and production environment accumulate in layers, and it is no longer possible to predictably reproduce the build on another machine.
Separate configuration moves differences into environment variables, mounted files, and overrides in a separate environment description.
The image remains unchanged, and exactly the same one that was tested on staging is promoted: Docker Compose substitutes parameters at startup, not at build time.
Hence the deployment time: an update comes down to restarting the service with a new set of parameters, without rebuilding and running the whole environment again. Rollback is a return to the previous set of variables, tens of seconds instead of rebuilding and re-validation.
Service downtime is limited to the stop time of one container, not the entire stack, if you restart one at a time.
Recovery is accelerated because state is moved to volumes and external services, and the container itself becomes replaceable.
A whole class of failures goes away, where rebuilding the image carried away accumulated data or left the environment half-built: bringing up the environment from scratch means bringing up the same images with the same parameters.
The share of incidents caused by environment divergence falls: checks run on the same artifact that will go into production, and the only difference remains in parameter values. Build reproducibility stops depending on who edited the configuration on the server and in what order.
Ways to organize environments: comparison of options
The choice of how to describe environments determines how much manual work each release costs and how often an edit goes to the wrong environment.
Below are four schemes that are actually used for staging and production, and the boundaries beyond which each starts to get in the way.
| Option | When it fits | Limitations |
|---|---|---|
| One common configuration file | one environment, small project, early stage | edits for different environments live side by side, the risk of affecting the production environment grows with each new service |
| Overlay files | base set of services is common, differences are ports, limits, variables | with three or more environments, combinations of overlays become difficult to track manually |
| Environment templates with value substitution | values come from environment variables and secrets | without template validation, a typo in a variable name surfaces only at startup |
| Separate configuration repository | multiple teams and environments, review and change history are needed | requires an access policy and synchronization with service repositories |
In practice, the schemes are almost always combined: the base set of services is kept in one file, differences are moved into overlays, and specific values into templates.
A separate configuration repository is justified when there are more than two environments and a change must pass review before rollout.
We choose an option based on the number of environments and the frequency of edits, not out of habit: an extra layer of abstraction here costs more than its absence.
Implementation order: from audit to handover
We start the work not by rewriting files, but with an inventory: compose files, overlay files for staging, environment variables, external volumes, and networks.
Often the difference between production and test environments is held in one engineer's head, not in the configuration.
- Configuration audit. Composition of services, image sources, volumes, ports, duplicated parameters. We mark places where the environment is substituted manually.
- Environment scheme. Base file plus overlays for staging and production; passwords and tokens move out of the repository into a separate store.
- Service configuration. Image tags, resource limits, restart policy, liveness checks, startup order of dependencies.
- Staging validation. Full cycle: bringing up from scratch, update, rollback to the previous tag, failure of an individual service, node restart.
- Deployment policy. Procedure, stop criteria, log reading, rollback procedure, and actions when the data schema changes.
- Documentation handover. Service map, network and volume diagram, variable description, on-call instructions, checklist of typical failures.
The output is a reproducible process: the same set of commands brings up staging and production, updates and rollbacks take minutes, and knowledge of the setup does not remain with one person.
Handover artifacts: files, schemes, policies
The customer receives not a configured server, but a kit that lets the environment be brought up from scratch on another machine and handed over to a new engineer without verbal explanations. Below is what exactly is recorded in the repository and documentation.
- Environment files — base compose file plus overrides for staging and production: limits, volumes, and external service addresses differ, not the application logic.
- Environment variable list — which are mandatory, which have default values, and what is supplied from the secrets store instead of lying in the repository.
- Service scheme — dependency map: who communicates with whom, which ports are published externally, which volumes survive a container restart.
- Access policies — roles in the repository and control panel, who is allowed to deploy to production, how access is granted and revoked.
- Rollback policy — step-by-step procedure for a failed release: return to the previous image, state checkpoints, how long it takes the environment to return to a working state.
- Checks and observability — service readiness conditions, restart rules, where logs and alerts go, by what signs degradation is visible.
- Team instructions — how to bring up the environment on your machine, how to add a new service, how the image is built and published.
The set is updated along with the infrastructure: we check the discrepancy between documentation and the actual state of the environment on every release.
Case study: how two environments stopped diverging in settings
Initially, staging and production lived in two independent sets of files: different service names, their own environment variables and volumes, readiness checks configured only in production.
Edits in staging did not reach production, and before each release an engineer manually compared configurations — the comparison took 40–60 minutes, and discrepancies surfaced only on live traffic.
We reduced both environments to a single description on Docker Compose: a base file with services, networks, and readiness checks, with two override files on top where only environment variables, resource limits, and restart policy differ.
Secrets are moved to separate environment files outside the repository, and startup order is set through dependency readiness conditions.
Deployment time dropped from 45 to 8 minutes: build and launch run with one command, no manual steps remain on the server.
The number of configuration-related incidents fell from 5–6 per quarter to zero over a comparable period, and the share of manual edits during deployment went from a third to zero.
The key effect is not automation itself, but that staging stopped being “approximately similar” to production. Rollback is also described by configuration: the previous set is launched with the same command, and data state is preserved in volumes.
What to do if staging and production have already diverged?
Divergence almost never starts with the configuration file. It appears from manual edits on the production server: someone adjusted a variable, added a volume, changed the restart policy — and did not return it to the repository.
Therefore, we first capture the actual state of running containers and compare it with what is described in the files: the discrepancy in settings at this level is usually wider than expected.
Next, we establish the source of truth. A common base file describes what is the same: images, dependencies, internal networks, health checks, startup order.
Differences — addresses, connection parameters, limits, port publication, data mounts — go into per-environment override files. Then staging and production differ by a short and predictable list, not by two independent descriptions.
The alignment plan is built by priorities, not by convenience of edits. First, what creates irreversible differences: data schema, migrations, file formats, volume contents — a mistake here is the most costly. Then application configuration and environment variables.
Last, runtime settings: limits, restart policies, service placement; their edits are reversible and are applied by rolling restart.
Downtime is not mandatory: we move services one at a time, checking the result after each step. We make migrations expandable — first both environments support the new format, and only then the old one is removed.
We finish with a rule: any manual edit on the server is either transferred to a file or rolled back, and environment comparison becomes a regular check during delivery. Otherwise they will diverge again.
Secrets, volumes, and migrations: common technical questions
Secrets in Compose do not go into the service description file: only variable names remain there, and the runtime substitutes values from an external store.
Staging and production have different sets — addresses, logging levels, access to external services — while the startup logic is the same, so behavior matches.
Data lives in named volumes, separate from the container lifecycle: rebuilding the image or restarting the service does not affect it, and deleting a volume remains a deliberate operation with a copy.
Schema migrations run as a separate step before the application starts — a new version of the code should not encounter the old database state.
What happens to data when a release is updated?
Nothing: volumes are not recreated along with the container. The update replaces the image, and the volume contents remain in place. Before risky migrations, we make a copy and test recovery on staging.
Can a release be rolled back?
Code — by returning the previous image tag, this takes minutes. The database schema is rolled back only by a pre-prepared reverse migration step; if it is unsafe, by restoring from a copy.
How are environments separated?
A common service description template plus separate value sets for staging and production. The differences are connection parameters and external service profiles, not diverged startup instructions.
Who gets access to secrets?
Only the services that need them, and the on-call engineer — through an access log. Value rotation is scheduled: it does not require rebuilding images and does not stop the environment.
Let's discuss the task and come back with an estimate and plan
An estimate for such a task cannot be given “by eye” — it depends on how many services there are, how they are connected, and what is already automated.
Therefore, we ask you to send what you have: current compose files, build instruction images, environment variables without secrets. From them it is immediately clear where dependencies are hardwired and where they are already moved into configuration.
- Service and connection scheme: who communicates with whom, which volumes and networks are shared, what must be accessible externally.
- Environment differences: how staging differs from production in resources, replicas, variables, and external integrations.
- How deployment and rollback are currently set up: scripts, continuous integration configuration, manual steps, downtime during deployment.
- Target infrastructure: number of hosts, where images are stored, how secrets are issued, network restrictions.
- Data handling: schema migrations, backups, what must not be lost when containers restart.
- Constraints: maintenance windows, security policies, expected launch timelines.
From this set, we assemble a work estimate, a phased implementation plan, team composition, and timelines — without assumptions that later turn into rework. If some data is missing, that is also a working case: we will say what needs to be collected before the start. Describe the task — we will come back with an estimate and a plan.







