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.
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
- It describes three services: PHP, nginx, and the database.
- nginx then uses the php service name as its internal address.
- 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
- This image contains PHP-FPM and the PostgreSQL driver.
- 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
- Save it as docker/nginx/default.conf.
- nginx sends PHP files to the php service on port 9000.
- 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.
-
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 -
Check their state
Look for the running status on all three services. If one exits immediately, look at its logs.
docker compose ps -
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.