php runtime and server environment

Docker Compose For Local Services

The important skill is understanding service boundaries. Inside Compose, containers talk to each other by service name, not by localhost. From your host machine, you usually use published ports.

A typical local shape

services:
  web:
    image: nginx:1.27
    ports:
      - "8080:80"
    volumes:
      - .:/var/www/app
    depends_on:
      - php

  php:
    build: .
    volumes:
      - .:/var/www/app
    environment:
      DB_HOST: database
      CACHE_HOST: redis

  database:
    image: mysql:8.4
    environment:
      MYSQL_DATABASE: app
      MYSQL_USER: app
      MYSQL_PASSWORD: local-password
      MYSQL_ROOT_PASSWORD: root-password
    volumes:
      - database-data:/var/lib/mysql

  redis:
    image: redis:7

volumes:
  database-data:

The host opens http://localhost:8080. The PHP container connects to MySQL using host database, because that is the Compose service name.

Service names are DNS names

Inside the php container:

DB_HOST=database
CACHE_HOST=redis

Using localhost from inside php means "the PHP container itself", not the MySQL container and not your laptop.

This is one of the most common Compose mistakes for PHP developers.

Database and cache persistence

Named volumes keep data after containers stop:

volumes:
  database-data:

That is convenient, but it means local data can become stale. When debugging strange database behaviour, know whether the project expects you to reset the volume, run migrations, or seed data.

Startup Order Is Not Readiness

depends_on controls startup order. It does not prove that MySQL has finished starting or that Redis is ready to accept connections.

A PHP container may start before the database is usable. The application should handle short-lived connection failures sensibly, and local tooling may need a health check or a small wait script before migrations run.

database:
  image: mysql:8.4
  healthcheck:
    test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
    interval: 5s
    timeout: 3s
    retries: 10

When a local stack fails only during startup, check readiness before changing application code.

Published Ports And Internal Ports

A published port lets the host reach a container:

ports:
  - "8080:80"

The host uses port 8080. The web container still listens on port 80.

Containers on the same Compose network usually do not need published ports to reach one another. Publish a database port only when a host tool, such as a desktop SQL client, needs it. Avoid exposing services without a reason.

Queue workers

A local queue worker is often another service using the same PHP image:

worker:
  build: .
  command: php artisan queue:work
  volumes:
    - .:/var/www/app
  depends_on:
    - database
    - redis

The exact command depends on the framework. The concept is the same: the worker is a separate long-running PHP process and needs the same code, environment, and service access as the web application.

Local secrets

Compose files often contain local-only passwords. Do not copy production secrets into a local Compose file. Use .env files carefully and keep real secrets out of Git.

Commands You Will Use Often

Learn the basic lifecycle commands:

docker compose up -d
docker compose ps
docker compose logs php
docker compose exec php php -v
docker compose exec php composer install
docker compose down

Use docker compose ps to see which services are running. Use logs when a service exits or a request fails. Use exec to run PHP and Composer in the actual project container.

If the Dockerfile or installed extensions change, rebuild the image:

docker compose up -d --build

What you should be able to do

After this lesson, you should be able to read a Compose file, identify PHP, web, database, cache, and worker services, explain service-name networking, understand volumes and published ports, distinguish startup order from readiness, and diagnose the common mistake of using localhost inside a container.

Practice

Task: Sketch A Compose Setup

Write a small Compose-style outline for a local PHP application.

Requirements

  • Include separate web, php, database, and redis services.
  • Show PHP connecting to the database by service name.
  • Include a named database volume.
  • Include one queue worker service or explain where it would fit.
  • Add a short note explaining why localhost is usually wrong from inside the PHP container.

Check your work

The answer should make service boundaries clear enough for a new developer to debug connection problems.

Show solution
services:
  web:
    image: nginx:1.27
    ports:
      - "8080:80"
    volumes:
      - .:/var/www/app
    depends_on:
      - php

  php:
    build: .
    volumes:
      - .:/var/www/app
    environment:
      DB_HOST: database
      REDIS_HOST: redis

  database:
    image: mysql:8.4
    environment:
      MYSQL_DATABASE: app
      MYSQL_USER: app
      MYSQL_PASSWORD: local-password
      MYSQL_ROOT_PASSWORD: root-password
    volumes:
      - database-data:/var/lib/mysql

  redis:
    image: redis:7

  worker:
    build: .
    command: php artisan queue:work
    volumes:
      - .:/var/www/app
    depends_on:
      - database
      - redis

volumes:
  database-data:

From inside the php container, database and redis are DNS names for the other Compose services. localhost would point back to the PHP container itself, so it is usually wrong for database and cache connections inside Compose.