Based on Docker documentation and best practices, here's a recommended folder structure for a Docker Compose application involving multiple services, environment-specific configurations, and profiles.
my-app/
├── docker/ # Docker related files
│ ├── common/ # Shared Dockerfile stages/components
│ │ └── base.Dockerfile # Base image definitions (optional)
│ ├── development/ # Development-specific Dockerfiles
│ │ ├── service1/
│ │ │ └── Dockerfile # Dev Dockerfile for service1
│ │ └── service2/
│ │ └── Dockerfile # Dev Dockerfile for service2
│ └── production/ # Production-specific Dockerfiles
│ ├── service1/
│ │ └── Dockerfile # Prod Dockerfile for service1
│ └── service2/
│ └── Dockerfile # Prod Dockerfile for service2
├── services/ # Application code for each service
│ ├── service1/
│ │ ├── src/ # Service1 source code
│ │ └── .dockerignore # Service-specific dockerignore
│ └── service2/
│ ├── src/ # Service2 source code
│ └── .dockerignore # Service-specific dockerignore
├── compose.yaml # Base Compose file (common services/config)
├── compose.dev.yaml # Development environment overrides
├── compose.prod.yaml # Production environment overrides
└── .dockerignore # Root dockerignore (applies to all builds by default)
This structure is inspired by examples like those provided for Laravel in the Docker documentation. [Laravel Production] [Laravel Development]
docker/ directory)docker/development/service1/Dockerfile, docker/production/service1/Dockerfile)
This allows tailoring build processes. Development builds might include debugging tools and linters, while production builds focus on optimization, security hardening, and smaller image sizes (often using multi-stage builds).
docker/common/base.Dockerfile)
Optionally use a common directory for shared Dockerfile stages or base configurations to avoid repetition (DRY principle). You can reference these stages in your environment-specific Dockerfiles.
compose.yaml:
Define your core services, networks, and volumes that are common across all environments. This file acts as the foundation.
compose.dev.yaml, compose.prod.yaml)
Use these files to override or extend the base configuration. Examples: mapping different ports, mounting source code for development, setting environment variables, specifying different Dockerfiles, or adding debug services.
services/ directory)services/service1/src). This clearly separates concerns.
Profiles allow you to selectively enable groups of services, ideal for optional tools or specific run modes (e.g., debugging, testing).
Define profiles within your Compose file(s):
services:
frontend:
build: ./services/frontend
profiles: [frontend, dev] # Belongs to 'frontend' and 'dev' profiles
# ... other frontend config
phpmyadmin:
image: phpmyadmin
profiles: [debug] # Only runs when 'debug' profile is active
# ... other phpmyadmin config
backend:
build: ./services/backend
# No 'profiles' key means this service is always enabled by default
# ... other backend config
Activate specific profiles using the --profile flag:
# Run default services + services with the 'dev' profile
docker compose --profile dev up
# Run only services with the 'debug' profile (and defaults if not specified otherwise)
docker compose --profile debug up -d
# Run multiple profiles
docker compose --profile frontend --profile debug up
Use .dockerignore files to exclude files and directories from the build context sent to the Docker daemon, speeding up builds and reducing image size.
.dockerignore: Place a general .dockerignore in the project root (my-app/.dockerignore). This applies by default when the build context is the root. Useful for ignoring common files like .git, node_modules (if not needed in build), logs, etc.
.dockerignore: Place additional .dockerignore files inside specific service directories (e.g., services/service1/.dockerignore). These are typically used when the build context in your compose.yaml is set to that service directory (e.g., build: ./services/service1).
.dockerignore: For maximum control, you can name an ignore file after the Dockerfile it applies to (e.g., service1.Dockerfile.dockerignore). This is less common but useful in complex scenarios.
[.dockerignore Files]
Combine the base and environment-specific Compose files using the -f flag:
# Run Development Environment (merges compose.yaml and compose.dev.yaml)
docker compose -f compose.yaml -f compose.dev.yaml up -d --build
# Run Production Environment (merges compose.yaml and compose.prod.yaml)
docker compose -f compose.yaml -f compose.prod.yaml up -d
This structure helps manage configuration overrides and keeps relative paths within the Compose files consistent, addressing potential issues mentioned in the documentation regarding merging files from different directories. [Merge Compose files]