A docker-compose.yml builder that turns a list of services — each with an image, ports, environment variables, volumes, and dependencies — into a valid Compose file complete with a top-level volumes block and a shared bridge network. It is built for developers spinning up a multi-container stack without hand-writing YAML and worrying about indentation.
How it works
Each service you add becomes one entry under the top-level services: key. The builder
emits the image, a sensible restart: unless-stopped, a quoted list of ports, an
environment map, a volumes list, and a depends_on list. Every service is attached to
a single declared appnet bridge network so containers can resolve one another by service
name.
Volume mounts are inspected: a source like db-data:/var/lib/postgresql/data is recognised
as a named volume and collected into the top-level volumes: block, while a source like
./src:/app is treated as a bind mount and left inline. This keeps the generated file
valid — named volumes must be declared, bind mounts must not.
A complete generated example
For a web app with an API and a Postgres database, the builder produces something like:
services:
api:
image: myapp/api:latest
restart: unless-stopped
ports:
- "3000:3000"
environment:
DATABASE_URL: postgres://postgres:${DB_PASSWORD}@db:5432/app
volumes:
- ./logs:/app/logs
depends_on:
db:
condition: service_healthy
networks:
- appnet
db:
image: postgres:17-alpine
restart: unless-stopped
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: app
volumes:
- db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
networks:
- appnet
volumes:
db-data:
networks:
appnet:
driver: bridge
The db-data named volume is declared at the top level because Docker manages it. The ./logs bind mount is left inline because it is a host path. The healthcheck on the database lets the API’s depends_on: condition: service_healthy block until Postgres is actually accepting connections.
Common mistakes to avoid
Using localhost for inter-service communication. Inside the Compose network each service’s hostname is its service name — the API should connect to db:5432, not localhost:5432. Localhost refers to the container itself.
Hardcoding secrets in environment values. The environment: block is fine for local development, but commit POSTGRES_PASSWORD=${DB_PASSWORD} and keep the real value in a .env file that is git-ignored.
Not adding a healthcheck to stateful services. depends_on without a condition: service_healthy only controls start order, not readiness. The database process starts before Postgres is actually ready to accept connections. Without a healthcheck, your API may crash on startup while the database is still initialising.
Forgetting docker compose config. Running this command after pasting your file has Docker validate and print the fully-resolved YAML, including variable substitution. It catches YAML indentation errors and missing variable values before you bring the stack up.
Useful Compose commands
Once your file is ready, these commands cover the typical development workflow:
# Start all services in the background
docker compose up -d
# Tail logs for all services
docker compose logs -f
# Restart a single service after code change
docker compose restart api
# Shut down and remove containers (keep volumes)
docker compose down
# Shut down and also remove named volumes
docker compose down -v
# Validate and print resolved YAML
docker compose config