Docker Compose Structure
The docker-compose.yml file defines the entire multi-container application. Itβs located in the root of the project and orchestrates four services.
Complete Configuration
services:
postgres:
image: postgres:16-alpine
container_name: laravel_postgres
environment:
POSTGRES_DB: laravel
POSTGRES_USER: laravel
POSTGRES_PASSWORD: secret
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- laravel_network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U laravel -d laravel"]
interval: 5s
timeout: 3s
retries: 20
app:
build:
context: .
dockerfile: docker/php/Dockerfile
args:
UID: "1000"
GID: "1000"
container_name: laravel_app
working_dir: /var/www/html
volumes:
- ./:/var/www/html
environment:
DB_CONNECTION: pgsql
DB_HOST: postgres
DB_PORT: 5432
DB_DATABASE: laravel
DB_USERNAME: laravel
DB_PASSWORD: secret
depends_on:
postgres:
condition: service_healthy
networks:
- laravel_network
nginx:
image: nginx:1.27-alpine
container_name: laravel_nginx
ports:
- "8000:80"
volumes:
- ./:/var/www/html:ro
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
- app
networks:
- laravel_network
node:
image: node:22-alpine
container_name: laravel_node
working_dir: /var/www/html
user: "0:0"
volumes:
- ./:/var/www/html
- node_modules:/var/www/html/node_modules
ports:
- "5173:5173"
command: sh -lc "chown -R 1000:1000 /var/www/html/node_modules && npm ci || npm i && npm run dev -- --host 0.0.0.0 --port 5173 --strictPort"
volumes:
postgres_data:
node_modules:
networks:
laravel_network:
driver: bridge
Service Configuration Breakdown
PostgreSQL Service
Environment Variables
Health Check
Volumes
| Variable | Value | Description |
|---|
POSTGRES_DB | laravel | Default database name |
POSTGRES_USER | laravel | Database user |
POSTGRES_PASSWORD | secret | Database password |
Change POSTGRES_PASSWORD in production environments. Never commit production credentials to version control.
healthcheck:
test: ["CMD-SHELL", "pg_isready -U laravel -d laravel"]
interval: 5s
timeout: 3s
retries: 20
- Test: Uses
pg_isready to verify database is accepting connections
- Interval: Checks every 5 seconds
- Timeout: Each check times out after 3 seconds
- Retries: Attempts up to 20 times before marking as unhealthy
volumes:
- postgres_data:/var/lib/postgresql/data
Named volume ensures database data persists across container restarts and rebuilds.
Laravel App Service
Build Configuration
Environment Variables
Dependencies
Volume Mount
build:
context: .
dockerfile: docker/php/Dockerfile
args:
UID: "1000"
GID: "1000"
- Context: Build context is the project root
- Dockerfile: Custom PHP-FPM image at
docker/php/Dockerfile
- Build Args: User/Group IDs for permission alignment with host
The UID/GID match the host user, preventing permission issues with mounted volumes.
| Variable | Value | Purpose |
|---|
DB_CONNECTION | pgsql | Laravel database driver |
DB_HOST | postgres | Database host (service name) |
DB_PORT | 5432 | PostgreSQL port |
DB_DATABASE | laravel | Database name |
DB_USERNAME | laravel | Database user |
DB_PASSWORD | secret | Database password |
These override Laravelβs .env file settings.depends_on:
postgres:
condition: service_healthy
The app container waits for PostgreSQL to pass health checks before starting.volumes:
- ./:/var/www/html
Mounts the entire project directory for live code reloading during development.
Nginx Service
Port Mapping
Volume Mounts
Nginx Configuration
Maps container port 80 to host port 8000. Access the application at http://localhost:8000.You can change the host port (left side) if 8000 is already in use: "3000:80"
volumes:
- ./:/var/www/html:ro
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
- Project files: Mounted read-only (
:ro) since Nginx only serves files
- Configuration: Custom Nginx config for Laravel routing
server {
listen 80;
server_name _;
root /var/www/html/public;
index index.php;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass app:9000;
}
location ~ /\. {
deny all;
}
}
- Root: Points to Laravelβs
public directory
- PHP Processing: Forwards to
app:9000 (PHP-FPM)
- Security: Denies access to hidden files
Node Service
Command
User Configuration
Volume Mounts
Port Mapping
command: sh -lc "chown -R 1000:1000 /var/www/html/node_modules && npm ci || npm i && npm run dev -- --host 0.0.0.0 --port 5173 --strictPort"
Startup sequence:
- Fix permissions on
node_modules directory
- Install dependencies (
npm ci or fallback to npm i)
- Start Vite dev server on all interfaces
The --host 0.0.0.0 flag allows access from the host machine.
Runs as root initially to fix permissions, then switches to UID 1000. volumes:
- ./:/var/www/html
- node_modules:/var/www/html/node_modules
- Project files: For source code access
- node_modules: Separate volume for better performance and isolation
Vite dev server with hot module replacement (HMR) accessible at http://localhost:5173.
Volume Configuration
Named Volumes
volumes:
postgres_data:
node_modules:
postgres_data
Persists PostgreSQL data files across container lifecycles. Data survives docker-compose down.
node_modules
Caches Node.js dependencies. Improves build performance and prevents host/container conflicts.
Volume Management Commands
# List volumes
docker volume ls
# Inspect a volume
docker volume inspect macuin_postgres_data
# Remove volumes (data loss!)
docker volume rm macuin_postgres_data
# Remove all unused volumes
docker volume prune
Removing the postgres_data volume will delete your database. Always backup data before removing volumes.
Network Configuration
networks:
laravel_network:
driver: bridge
The bridge network driver creates an isolated network for service-to-service communication.
Network Features
- DNS Resolution: Services can reference each other by name
- Isolation: Separated from host network and other Docker networks
- Port Publishing: Only explicitly mapped ports are accessible from host
Dockerfile Configuration
PHP Application Dockerfile
FROM php:8.3-fpm
ARG UID=1000
ARG GID=1000
RUN apt-get update && apt-get install -y \
git curl unzip zip \
libpq-dev \
&& docker-php-ext-install pdo pdo_pgsql \
&& rm -rf /var/lib/apt/lists/*
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
RUN groupadd -g ${GID} appuser \
&& useradd -m -u ${UID} -g ${GID} appuser
WORKDIR /var/www/html
USER appuser
Base Image
Build Arguments
Dependencies
Composer
User Setup
Official PHP 8.3 with FastCGI Process Manager for production-ready performance. ARG UID=1000
ARG GID=1000
Configurable user/group IDs passed from docker-compose.yml.RUN apt-get update && apt-get install -y \
git curl unzip zip \
libpq-dev \
&& docker-php-ext-install pdo pdo_pgsql \
&& rm -rf /var/lib/apt/lists/*
Installs:
- Git tools: For Composer VCS dependencies
- PostgreSQL libraries: Required for pdo_pgsql extension
- PHP extensions: PDO and PostgreSQL driver
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
Copies Composer binary from official image using multi-stage build.RUN groupadd -g ${GID} appuser \
&& useradd -m -u ${UID} -g ${GID} appuser
USER appuser
Creates non-root user matching host UID/GID for proper file permissions.
Docker Ignore Configuration
The .dockerignore file excludes unnecessary files from the build context:
.git
node_modules
vendor
storage/logs
storage/framework/cache
storage/framework/sessions
storage/framework/views
.env
Excluding these files reduces build context size and improves build speed.
Customizing Configuration
Change Database Credentials
- Update environment variables in
docker-compose.yml:
postgres:
environment:
POSTGRES_DB: myapp
POSTGRES_USER: myuser
POSTGRES_PASSWORD: strongpassword
- Update corresponding variables in the
app service:
app:
environment:
DB_DATABASE: myapp
DB_USERNAME: myuser
DB_PASSWORD: strongpassword
Change Host Ports
Modify the left side of port mappings:
nginx:
ports:
- "3000:80" # Changed from 8000 to 3000
node:
ports:
- "5174:5173" # Changed from 5173 to 5174
Customize User IDs
If your host user has a different UID/GID:
app:
build:
args:
UID: "1001" # Your actual UID
GID: "1001" # Your actual GID
Find your UID/GID with: id -u and id -g
Add Environment Variables
Extend the app service environment:
app:
environment:
DB_CONNECTION: pgsql
# ... existing vars ...
MAIL_MAILER: smtp
MAIL_HOST: mailhog
MAIL_PORT: 1025
Next Steps
Services
Learn how to manage individual services
Overview
Review the architecture overview