Local development

How to create a Docker environment for PHP, nginx, and PostgreSQL

Three services, one command to start them, and the same environment for the whole team.

20 minutes · Docker Compose · PHP project

What we are building

PHP runs separately. nginx does too. So does the database.

Docker Compose describes three cooperating services in one file. PHP handles code, nginx accepts web requests, and PostgreSQL stores data.

This is not a production template for everything. It is a clean, readable baseline for local PHP application development.

Get ready

What you need first

If Docker is not installed yet, start with the installation guide.

  • Docker Desktop or Docker Engine with the Compose plugin.
  • A PHP project directory. The example expects a public/index.php entry point; Symfony already has one.
  • Free port 8080. Publish database port 5432 only when you really need it from your computer.
  • A text editor. Configuration files belong in Git; passwords do not.

Step by step

Add three small configuration files

In the project root, create compose.yaml, Dockerfile, and docker/nginx/default.conf. Then add the following content.

1. compose.yaml

  1. It describes three services: PHP, nginx, and the database.
  2. nginx then uses the php service name as its internal address.
  3. The password is only for this local example. Production needs secure configuration.
services:
  php:
    build: .
    volumes:
      - .:/var/www/html
    depends_on:
      db:
        condition: service_healthy

  nginx:
    image: nginx:alpine
    ports:
      - "127.0.0.1:8080:80"
    volumes:
      - .:/var/www/html:ro
      - ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
    depends_on:
      - php

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: app
      POSTGRES_USER: app
      POSTGRES_PASSWORD: change-me
    ports:
      - "127.0.0.1:5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d app"]
      interval: 5s
      timeout: 3s
      retries: 10

volumes:
  postgres_data:
Docker Compose documentation

2. PHP Dockerfile

  1. This image contains PHP-FPM and the PostgreSQL driver.
  2. Always align the PHP version with the project. Add Composer later if the project needs it.
FROM php:8.4-fpm-alpine

RUN docker-php-ext-install pdo_pgsql

WORKDIR /var/www/html
Official PHP image

3. nginx configuration

  1. Save it as docker/nginx/default.conf.
  2. nginx sends PHP files to the php service on port 9000.
  3. The example expects the public application entry point in the public directory.
server {
    listen 80;
    server_name _;
    root /var/www/html/public;
    index index.php;

    location / {
        try_files $uri /index.php$is_args$args;
    }

    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_pass php:9000;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
}
nginx documentation

Start and check

Now let Docker do the work

Run these commands from the project root, where compose.yaml is located.

  1. Build and start the services

    The first run downloads images and builds the PHP image. The -d option keeps services running in the background.

    docker compose up --build -d
  2. Check their state

    Look for the running status on all three services. If one exits immediately, look at its logs.

    docker compose ps
  3. Open the application

    Open http://localhost:8080 in a browser. A Symfony project should show the application or its default page.

    docker compose logs --tail=50 nginx

Common snags

When it does not work on the first try

Port 8080 or 5432 is already in use

Change the port on the left side of compose.yaml, for example to "127.0.0.1:8081:80". Keep the service port on the right unchanged.

nginx returns 502 Bad Gateway

The php service is probably not running or its image did not build. Check its logs and verify that the nginx configuration still says fastcgi_pass php:9000.

docker compose logs --tail=80 php
The application cannot connect to the database

PostgreSQL is not localhost inside the Docker network. In application configuration, use host db, user app, database app, and the password from compose.yaml.

I want a fresh database

A Docker volume keeps data even after services stop. The following command therefore deletes local database data. Use it only when you can really discard it.

docker compose down --volumes

Done

You have a repeatable local environment.

Docker keeps the environment description in code. The next step is adding Composer, environment variables, and migrations for the particular application.

Request a call

I will call you on the next working day between 9:00 and 17:00.

You can also call me directly.

+420 605 181 728

Leave your phone number and send a callback request.

By sending, you agree to processing your data in order to handle your request.