dockercomposeyamlcontainerdevopsdeveloper

Docker Compose: Writing, Validating, and Debugging docker-compose.yml Files

How to write clean docker-compose.yml files, understand service definitions, port mappings, volumes, and networks, and validate configs before deploying.

7 min read

Related Tool

Docker Compose Validator

Open tool

What Docker Compose Does

Docker Compose lets you define and run multi-container applications with a single YAML file. Instead of remembering long docker run commands with every flag, you declare all services, their images, environment variables, ports, volumes, and network connections in a docker-compose.yml, then start everything with docker compose up.

The Basic Structure

version: "3.9"

services:
  web:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./html:/usr/share/nginx/html

  db:
    image: postgres:15
    environment:
      POSTGRES_PASSWORD: secret
    volumes:
      - db_data:/var/lib/postgresql/data

volumes:
  db_data:

services — each named block defines a container. image specifies which Docker image to pull. ports maps host:container ports. volumes mounts host directories or named volumes into containers. environment sets environment variables.

Build vs Image

If you have a Dockerfile, use build: . instead of image::

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "3000:3000"

You can specify a custom Dockerfile path and build arguments using the build block.

Dependencies Between Services

depends_on controls startup order:

services:
  app:
    depends_on:
      - db

Note: depends_on only waits for the container to start, not for the application inside it to be ready. Use a health check and condition: service_healthy if you need to wait until the database is accepting connections.

Networks

By default, all services in a Compose file share a default network and can reach each other by service name. For isolation, define explicit networks:

services:
  frontend:
    networks:
      - public
  backend:
    networks:
      - public
      - private
  db:
    networks:
      - private

networks:
  public:
  private:

Environment Variables and .env Files

Compose automatically reads a .env file in the same directory. Variables defined there are available in the docker-compose.yml via ${VARIABLE_NAME}:

environment:
  DATABASE_URL: ${DATABASE_URL}

Never commit .env files with real credentials. Use .env.example as a template and document required variables there.

Common Errors

Port already in use: another process is listening on the mapped host port. Change the host port mapping or stop the conflicting process. Volume permission errors: the process inside the container may not have permission to write to a bind-mounted directory. Check the UID/GID. Image not found: typo in the image name, or the image requires authentication. Run docker login if it's a private registry. YAML parsing errors: indentation mistakes are the most common cause. YAML uses spaces, not tabs.

Validating Before Deploying

Running docker compose config prints the fully resolved configuration with all variable substitutions applied — useful for checking that .env values are interpolated correctly. A compose validator can catch structural issues (missing required keys, invalid port mappings, unknown top-level keys) before you attempt a deploy. The DevHexLab Docker Compose Validator parses the YAML, checks service definitions, port mappings, and network references, and surfaces issues with explanations.

Production Considerations

Docker Compose is excellent for local development and small-scale deployments. For production at scale, Kubernetes or Docker Swarm provide better scheduling, health management, and rolling updates. Many teams use Compose for local dev and a different orchestrator for production, keeping the two environments as similar as possible.