DEV Community

Cover image for Solved: Docker compose single file or multiple yaml files?
Darian Vance
Darian Vance

Posted on • Originally published at wp.me

Solved: Docker compose single file or multiple yaml files?

🚀 Executive Summary

TL;DR: Docker Compose files often become unwieldy across development, staging, and production environments due to varying configurations. This guide presents three strategies—using multiple -f files, environment variables with .env files, or migrating to orchestration tools like Kubernetes—to manage configuration complexity and streamline deployments.

🎯 Key Takeaways

  • Docker Compose configurations frequently become complex when managing different requirements for local development, staging, and production environments.
  • The docker-compose -f command allows merging multiple YAML files, where configurations from later files override those in earlier ones, but requires careful command-line order.
  • Utilizing environment variables with .${VARIABLE:-default\_value} syntax in docker-compose.yml and separate .env files (e.g., –env-file .env.prod) is the recommended approach for most projects, offering cleaner automation and CI/CD integration.
  • For complex, mission-critical production systems requiring high availability and scaling, migrating beyond Docker Compose to tools like Kubernetes with Helm or Infrastructure as Code (Terraform/Pulumi) is necessary.
  • Sensitive environment configuration files (like .env.prod) should be added to .gitignore and their values managed through secrets managers in CI/CD pipelines for security.

Struggling with sprawling Docker Compose files? A Senior DevOps lead breaks down three real-world strategies—from simple command-line flags to environment-based configs—to tame your YAML and streamline your deployments across dev, staging, and prod.

One YAML to Rule Them All? A DevOps Lead’s Guide to Docker Compose Files

I still remember the 2 AM page. A “simple” deployment had taken down the entire staging environment. We spent forty minutes frantically rolling back, tracing logs, and apologizing to a very stressed-out QA lead. The cause? A developer had updated the resource limits in a new file, docker-compose.staging.yml, but our CI/CD pipeline, bless its simple heart, only knew about the original docker-compose.yml. It deployed the old code with the old config, and everything fell over. This classic dilemma—one giant file versus a constellation of smaller ones—isn’t just a style choice; it’s a landmine waiting to ruin your night.

So, Why Does This Always Become a Mess?

At the start of a project, a single docker-compose.yml is a thing of beauty. It’s clean, simple, and declarative. But projects grow. Suddenly, you have different needs for different environments:

  • Local Dev: You need to mount your source code as a volume for hot-reloading and maybe run a local Postgres container.
  • Staging: You need to pull a specific image tag from your registry and connect to a shared staging database, like stg-rds-cluster-01.
  • Production: You need higher resource limits, different environment variables for API keys, and it connects to prod-db-01. You absolutely do not want to mount source code here.

The single file bloats with comments and commented-out sections. The multiple-file approach leads to confusion about which file is the source of truth. The root cause is simple: we’re trying to make one tool solve three different problems. Luckily, we have ways to manage this chaos.

Solution 1: The Command-Line Warrior (Using Multiple Files with -f)

This is the most direct approach and the one you’ll see most often in tutorials. Docker Compose is designed to merge multiple files. You provide them in order, and values from later files override earlier ones. It’s a bit like layering transparencies.

You start with a base file, docker-compose.yml:

version: '3.8'

services:
  webapp:
    image: my-awesome-app:latest
    ports:
      - "8000:8000"
    environment:
      - DB_HOST=localhost
Enter fullscreen mode Exit fullscreen mode

Then, you create an override file for production, docker-compose.prod.yml:

version: '3.8'

services:
  webapp:
    image: my-registry/my-awesome-app:v1.2.5
    environment:
      - DB_HOST=prod-db-01.us-east-1.rds.amazonaws.com
      - REPLICA_COUNT=3
Enter fullscreen mode Exit fullscreen mode

To run it, you explicitly list the files on the command line:

docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d
Enter fullscreen mode Exit fullscreen mode

Heads Up: The order here is critical. The last file specified wins any conflicts. It’s powerful, but also incredibly easy for a junior dev (or a tired senior dev at 2 AM) to get the order wrong or forget a file entirely, leading to a broken deployment.

Solution 2: The Environment Variable Maestro (Using .env Files)

This is my preferred method for most projects. It keeps the structure in a single, primary docker-compose.yml but externalizes the configuration values that change between environments. It’s cleaner and less error-prone for automation.

Your docker-compose.yml becomes a template:

version: '3.8'

services:
  webapp:
    image: ${DOCKER_IMAGE_TAG:-my-awesome-app:latest}
    ports:
      - "8000:8000"
    environment:
      - DB_HOST=${DB_HOST:-localhost}
      - REPLICA_COUNT=${REPLICA_COUNT:-1}
Enter fullscreen mode Exit fullscreen mode

Notice the ${VARIABLE:-default_value} syntax. This makes the file usable for local development without any extra config. For production, you create a .env.prod file (and add it to .gitignore!):

# Production Environment Configuration
DOCKER_IMAGE_TAG=my-registry/my-awesome-app:v1.2.5
DB_HOST=prod-db-01.us-east-1.rds.amazonaws.com
REPLICA_COUNT=3
Enter fullscreen mode Exit fullscreen mode

Now, your deployment command is much cleaner and more reliable:

docker-compose --env-file .env.prod up -d
Enter fullscreen mode Exit fullscreen mode

This pattern is a lifesaver in CI/CD pipelines, where you can populate these environment variables directly from secrets managers (like AWS Secrets Manager or HashiCorp Vault) instead of a static file.

Solution 3: The “We’re Not in Kansas Anymore” Option (Moving Beyond Compose)

Let’s be honest. Sometimes, the problem isn’t the file structure; it’s that we’re using the wrong tool for the job. Docker Compose is a phenomenal tool for local development and simple, single-host deployments. Once you’re managing multiple environments with complex networking, zero-downtime deployments, and auto-scaling, you’ve outgrown it.

This is when you level up:

  • Kubernetes with Helm: This is the industry standard for container orchestration. Helm charts are the K8s equivalent of Compose files but are built from the ground up with templating (using Go templates) and environment management (via values.yaml files) in mind. The learning curve is steep, but the power is immense.
  • Infrastructure as Code (IaC): Tools like Terraform or Pulumi can be used to define and deploy your containerized services on platforms like AWS ECS or Azure Container Apps. You’re defining the *entire* infrastructure, not just the container runtime.

Moving to these tools isn’t a weekend project. It’s a strategic decision. But if you find yourself writing shell scripts to dynamically generate your Docker Compose files, you’re already halfway there in terms of need.

Comparison Breakdown

Approach Pros Cons Best For
1. Multiple Files (-f) Simple to understand, built-in, good for small overrides. Error-prone commands, can get confusing which file “wins”. Small projects or adding temporary, dev-only services (e.g., a test runner).
2. Env Variables (.env) Clean, great for CI/CD, separates config from logic, single source of truth for structure. Can lead to a huge number of env vars if not managed well. Most projects that have more than one environment. My default choice.
3. Moving Beyond Compose Extremely powerful, built for production scale, enables advanced deployment strategies. High learning curve, much more complex, overkill for simple apps. Complex, mission-critical production systems requiring high availability and scaling.

My Final Take

Stop trying to find the one “perfect” Docker Compose file. It doesn’t exist. For 90% of the teams I work with, the environment variable approach (Solution 2) is the sweet spot. It respects the simplicity of Compose while providing the flexibility needed for real-world development and deployment pipelines. Start there. And if that starts to feel painful, don’t be afraid to admit you might be ready to graduate to a bigger tool. The goal is to make deployments boring and predictable, not a 2 AM adventure.


Darian Vance

👉 Read the original article on TechResolve.blog


☕ Support my work

If this article helped you, you can buy me a coffee:

👉 https://buymeacoffee.com/darianvance

Top comments (0)