Have you ever opened four terminals to start your database, backend, frontend, and Redis every time you begin developing? And then had to remember long commands with ports, volumes, and networks? That's exactly the problem Docker Compose solves in one go.
We, at Meteora Web, use it daily. We coordinate local development for clients with complex stacks (PHP + MySQL + Redis + Node, or Laravel + PostgreSQL + Mailpit), and Docker Compose lets us replicate the production environment on every machine in seconds. No more "it works on my machine".
What is Docker Compose and why you need it
Docker Compose is a tool for defining and running multi-container applications. You write a docker-compose.yml file in YAML, describe services, networks, and volumes, and with a single command everything starts together.
Why it beats launching containers manually:
- Reproducibility: everything is declared in a file you can version with Git.
- Shareability: the whole team uses the same environment, zero differences.
- Isolation: each project has its own dependencies, no version conflicts (PHP, Python, Node, databases).
- Speed:
docker compose upand you're up and running.
Your first docker-compose.yml
Let's take a real example: a PHP web application (Laravel) with MySQL and Redis for caching. Here's the file:
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile
ports:
- "8080:80"
volumes:
- .:/var/www/html
depends_on:
- db
- redis
environment:
DB_HOST: db
REDIS_HOST: redis
db:
image: mysql:8.0
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: rootpass
MYSQL_DATABASE: myapp
MYSQL_USER: user
MYSQL_PASSWORD: userpass
volumes:
- dbdata:/var/lib/mysql
redis:
image: redis:alpine
ports:
- "6379:6379"
volumes:
dbdata:Key parts explained:
Services
Each container you need is a service. app is built from a Dockerfile, db and redis use public images. Service names become network hostnames: your PHP connects to MySQL using db as host.
Volumes
dbdata is a named volume: database data survives container stop and restart. The bind mount .:/var/www/html syncs your source code: you change a file and the server sees it immediately (no rebuild needed).
Dependencies
depends_on tells Docker Compose to start db and redis first. It doesn't guarantee the database is ready to accept connections (that requires health checks), but the startup order is correct.
Essential commands for daily work
Here are the commands you'll use every day:
| Command | What it does |
|---|---|
docker compose up -d | Start all services in the background (detached). |
docker compose down | Stop and remove containers, networks, but not volumes. |
docker compose build | Rebuild images (if you modified the Dockerfile). |
docker compose logs -f | Follow logs of all services in real time. |
docker compose exec app php artisan migrate | Run a command inside the app container (e.g., a migration). |
We run docker compose up -d every morning and docker compose down at night. Simple, fast, reproducible.
Environment variables and .env file
Hardcoding passwords in docker-compose.yml is a no-go. Use a .env file in the same directory. Docker Compose reads it automatically if you use ${VAR} syntax.
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASS}
MYSQL_DATABASE: ${DB_NAME}In your .env write:
DB_ROOT_PASS=supersecret
DB_NAME=myappImportant: don't commit the .env file to Git (add it to .gitignore). Share a .env.example with the team.
Overrides for local development
Often you want a clean docker-compose.yml for production, but locally you need to mount source code, enable xdebug, expose more ports. Docker Compose supports override files:
docker-compose.yml— base (shared with production).docker-compose.override.yml— local additions (can be versioned or not).
Example override for development:
services:
app:
volumes:
- .:/var/www/html
environment:
XDEBUG_MODE: debug
XDEBUG_CONFIG: client_host=host.docker.internalRun docker compose up -d and the override is applied automatically.
Health checks and waiting for the database
depends_on does not wait for the database to be ready. For apps that crash if the DB isn't available, add health checks:
db:
image: mysql:8.0
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 5s
timeout: 5s
retries: 5
app:
depends_on:
db:
condition: service_healthyNow the app container starts only after MySQL is healthy. Fewer broken tests.
Common mistakes and how to avoid them
Port conflicts: if you have a local MySQL on port 3306, either map the container to a different port (e.g., 3307) or stop your local service. Better yet: use internal ports and connect containers via the internal network.
Volume permissions: on Linux, files created by the container are owned by root. To use your local user, set the UID in the Dockerfile or use the user parameter in the service. We solve this by creating a dedicated user in the Dockerfile.
Cache and rebuilds: if you modify the Dockerfile, remember to docker compose build before up. Use layer caching efficiently (order instructions from least to most frequently changing).
Concrete workflow with Laravel
Here's how we use Docker Compose in a real project:
- Clone the repository.
- Copy
.env.exampleto.envand fill in your variables. - Run
docker compose up -d. - Enter the container:
docker compose exec app bash. - Install dependencies:
composer install. - Generate a key:
php artisan key:generate. - Run migrations:
php artisan migrate. - Open
http://localhost:8080.
All done in under 5 minutes on any machine with Docker installed.
What to do now
If you don't have a docker-compose.yml for your project, here's a checklist:
- Identify the services you need (web, db, cache, queue, etc.).
- For each service, choose an official image or write a Dockerfile.
- Define which ports to expose (only those needed in development).
- Mount your source code with a bind mount for hot reload.
- Use a named volume for persistent data (database).
- Add environment variables with a .env file.
- Test with
docker compose up -dand verify connectivity between services.
We at Meteora Web have automated this setup for every new project. If you want to dive deeper, check the official Docker Compose documentation. It's clear and well-written.
Remember: a reproducible development environment is not a luxury — it's an investment. It removes the burden of configuration headaches and lets you focus on what matters: writing great code.
Sponsored Protocol