NIXX/DEVv1.14.0
ArticlesFavorites
Sign In
Sign In
Articles

Welcome to our blog

A curated collection of insightful articles, practical guides, and expert tips designed to simplify your workflow

Cover image for: Dockerizing Your PHP Application for Local Development
September 30, 20255 MIN READ min readBy ℵi✗✗

Dockerizing Your PHP Application for Local Development

Tired of juggling different PHP versions or broken local setups? Docker makes local PHP development painless and consistent. In this guide, you’ll learn how to dockerize your PHP application with a custom Dockerfile, Docker Compose for services like MySQL, and best practices for smooth development.

phpwebdevdocker
ℵi✗✗

ℵi✗✗

Full-Stack Developer

Passionate about building tools and sharing knowledge with the developer community.

Was this helpful?

Popular Posts

  • NixOS vs. Arch Linux: Which One Belongs in Your Dev Setup?

    NixOS vs. Arch Linux: Which One Belongs in Your Dev Setup?

    5 MIN READ min read

  • How to Enable HTTPS on Localhost in Under 2 Minutes

    How to Enable HTTPS on Localhost in Under 2 Minutes

    3 MIN READ min read

  • Migrating from Create React App (CRA) to Vite: A Step-by-Step Guide

    Migrating from Create React App (CRA) to Vite: A Step-by-Step Guide

    4 MIN READ min read

  • Array Destructuring in PHP: A Practical Guide for Modern Developers

    Array Destructuring in PHP: A Practical Guide for Modern Developers

    5 MIN READ min read

Recommended Products

  • Apple iPad (7th Gen)

    Apple iPad (7th Gen)

    4.3
  • Fitbit Versa 4

    Fitbit Versa 4

    4.3
  • JBL Flip 6

    JBL Flip 6

    4.8
  • Dell 24 Monitor — SE2425HM Full HD

    Dell 24 Monitor — SE2425HM Full HD

    4.7

May contain affiliate links

Topics

webdev33productivity16cybersecurity12javascript11automation9guide8react7typescript7php6tutorial6freelancing5github actions5privacy5how to4Node.js4
+111 more topics →
🇺🇸USD ACCOUNTOpen a free US-based USD accountReceive & save in USD — powered by ClevaSponsoredInterserver Hosting#1 VALUEAffordable, reliable hosting from $2.50/mo99.9% uptimeSponsored

Local PHP development has a consistent set of friction points: managing multiple PHP versions across projects, configuring extensions, keeping MySQL running and accessible, and ensuring the setup is reproducible when a new team member joins or a fresh machine needs to be configured.

Docker addresses all of these by packaging the application and its dependencies into containers that run identically regardless of the host machine. The PHP version, extensions, Apache configuration, and MySQL version are all defined in version-controlled files. Spinning up the environment is a single command.

This guide builds a working PHP and MySQL development environment using a Dockerfile for the PHP service and Docker Compose for multi-service orchestration.

What this covers:

  • Installing Docker and verifying the setup

  • Writing a Dockerfile for PHP with Apache

  • Configuring Docker Compose for PHP and MySQL

  • Starting, stopping, and inspecting containers

  • Managing credentials with a .env file

  • Volume mounts for code changes and database persistence

  • A .dockerignore file and other practical considerations


Step 1: Verify Docker Is Installed

Check that both Docker and Docker Compose are available:

docker --version
docker compose version

If either is missing, install Docker Desktop from docker.com. Docker Desktop includes both the Docker daemon and the Compose plugin.


Step 2: Create the Dockerfile

In the PHP project root, create a Dockerfile:

# Official PHP image with Apache
FROM php:8.2-apache

# Install PHP extensions for MySQL connectivity
RUN docker-php-ext-install pdo pdo_mysql mysqli

# Enable mod_rewrite for URL routing (required by most PHP frameworks)
RUN a2enmod rewrite

# Set working directory
WORKDIR /var/www/html

# Copy project files into the container
COPY . /var/www/html/

This image includes Apache alongside PHP, so no separate web server container is needed for local development. The pdo, pdo_mysql, and mysqli extensions cover both PDO-based and procedural MySQL access patterns.

The COPY instruction comes after WORKDIR. When using volume mounts in development (covered in Step 4), the COPY is less critical because the host files overlay it — but keeping it in the Dockerfile ensures the image is self-contained if built without a volume mount.


Step 3: Create the Docker Compose File

Docker Compose defines the multi-service setup — PHP and MySQL — and their relationships:

services:
  app:
    build: .
    ports:
      - "8080:80"
    volumes:
      - .:/var/www/html
    depends_on:
      - db
    environment:
      MYSQL_HOST: db
      MYSQL_DATABASE: ${MYSQL_DATABASE}
      MYSQL_USER: ${MYSQL_USER}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}

  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${MYSQL_DATABASE}
      MYSQL_USER: ${MYSQL_USER}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
    volumes:
      - db_data:/var/lib/mysql

volumes:
  db_data:

What each section does:

The app service builds the PHP container from the Dockerfile in the current directory, maps port 8080 on the host to port 80 in the container, and mounts the project directory so code changes are reflected immediately. The depends_on directive ensures the db service starts before app.

The db service runs MySQL 8.0 with a named volume (db_data) that persists the database between container restarts. Without the named volume, the database is lost every time the container stops.

MYSQL_HOST: db in the app service passes the database hostname to PHP. Inside Docker Compose, services reach each other by their service name, so the PHP application connects to db rather than localhost.

Note: the version field at the top of docker-compose.yml is deprecated in current versions of Docker Compose and can be omitted.


Step 4: Manage Credentials with a .env File

Docker Compose reads a .env file in the same directory automatically. Create one with the database credentials:

MYSQL_ROOT_PASSWORD=rootpassword
MYSQL_DATABASE=myapp
MYSQL_USER=appuser
MYSQL_PASSWORD=apppassword

Add .env to .gitignore and commit a .env.example with placeholder values instead:

# .env.example
MYSQL_ROOT_PASSWORD=
MYSQL_DATABASE=
MYSQL_USER=
MYSQL_PASSWORD=

The docker-compose.yml file already references these as ${VARIABLE_NAME}, so no further changes are needed. The actual values never appear in version control.


Step 5: Add a .dockerignore File

A .dockerignore file prevents unnecessary files from being copied into the container image during the COPY step. This keeps the image smaller and avoids including development artefacts:

.git
.env
.env.*
node_modules
vendor
*.log
*.cache

Excluding vendor is worth noting: if Composer dependencies are managed inside the container rather than on the host, the vendor directory will be generated during the build rather than copied from the host. For local development with volume mounts, the vendor directory on the host is used directly.


Step 6: Start the Environment

Build and start both services:

docker compose up -d

The first run builds the PHP image and pulls the MySQL image. Subsequent runs reuse the cached layers and start much faster.

Visit http://localhost:8080 to confirm the PHP application is running.

Useful commands for managing the environment:

# View logs for all services
docker compose logs -f

# View logs for a specific service
docker compose logs -f app

# Open a shell inside the PHP container
docker exec -it $(docker compose ps -q app) bash

# Stop containers without removing volumes
docker compose stop

# Stop containers and remove them (volumes are preserved)
docker compose down

# Stop containers and remove volumes (deletes the database)
docker compose down -v

The distinction between stop and down matters: stop halts the containers, down removes them. Neither removes named volumes unless -v is passed.


Step 7: Connect PHP to MySQL

Inside PHP, the database host is the service name defined in docker-compose.yml — db — not localhost. A basic PDO connection:

<?php

$host = getenv('MYSQL_HOST') ?: 'db';
$dbname = getenv('MYSQL_DATABASE');
$user = getenv('MYSQL_USER');
$pass = getenv('MYSQL_PASSWORD');

try {
    $pdo = new PDO(
        "mysql:host=$host;dbname=$dbname;charset=utf8mb4",
        $user,
        $pass,
        [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
    );
    echo "Connected successfully";
} catch (PDOException $e) {
    echo "Connection failed: " . $e->getMessage();
}

PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION converts PDO errors into exceptions, which is the recommended setting for development and production.

One common issue: MySQL takes a few seconds to initialize on first start, and PHP may try to connect before MySQL is ready. depends_on in Compose ensures the container starts but does not wait for MySQL to be accepting connections. A retry loop or a healthcheck on the db service handles this properly:

db:
    image: mysql:8.0
    healthcheck:
        test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
        interval: 5s
        timeout: 5s
        retries: 10
app:
    depends_on:
        db:
            condition: service_healthy

With the healthcheck, Compose waits for MySQL to pass the health check before starting the app service.


Key Takeaways

  • A Dockerfile defines the PHP environment: the base image, extensions, and Apache configuration. A docker-compose.yml orchestrates PHP and MySQL together.

  • The app service reaches MySQL using the service name db as the host, not localhost.

  • Named volumes (db_data) persist the database between container restarts. docker compose down -v removes them.

  • Volume mounts (.:/var/www/html) reflect code changes in the container instantly without rebuilding the image.

  • Use a .env file for credentials and a .dockerignore to exclude development artefacts from the image.

  • The depends_on directive with a MySQL healthcheck prevents the PHP service from starting before MySQL is ready to accept connections.


Conclusion

A Docker-based PHP development environment solves the version management, extension configuration, and reproducibility problems that make local PHP setup time-consuming. The configuration is small, version-controlled, and portable: anyone with Docker installed can clone the repository and run docker compose up -d to get a working environment.

The setup described here is a solid foundation for local development. For production, the same Dockerfile can be extended with multi-stage builds, smaller base images, and production-specific Apache configuration.


Running into a specific Docker PHP configuration issue? Describe the error in the comments.

Topics
phpwebdevdocker
Interserver Hosting#1 VALUEAffordable, reliable hosting from $2.50/mo99.9% uptimeSponsored

Discussion

Join the discussion

Sign in to share your thoughts and engage with the community.

Sign In
Loading comments…

Continue Reading

More Articles

View all
Cover image for: Why You Should Use TypeScript in Every JavaScript Project
Jul 23, 20255 MIN READ min read

Why You Should Use TypeScript in Every JavaScript Project

JavaScript gets the job done—but TypeScript helps you write cleaner, safer, and easier-to-maintain code. Here’s why it’s worth using everywhere.

Cover image for: Embedding Cybersecurity in Development: Best Practices for 2025
Jul 1, 20257 MIN READ min read

Embedding Cybersecurity in Development: Best Practices for 2025

A developer-focused guide to integrating security into your workflow—covering tools, practices, and mindset shifts for 2025.

Cover image for: Best Web Hosting of 2026 (Honest Picks From Real-World Use)
Jan 1, 20267 MIN READ min read

Best Web Hosting of 2026 (Honest Picks From Real-World Use)

Choosing the right web hosting in 2026 isn't just about price. A breakdown of the best providers, focusing on reliability, performance, and support.

Cover image for: React Authentication with JWT: A Step-by-Step Guide
Oct 17, 20257 MIN READ min read

React Authentication with JWT: A Step-by-Step Guide

Learn how to implement secure JWT authentication in React. From login to route protection and API calls, this guide covers everything you need to know.

|Made with · © 2026|TermsPrivacy
AboutBlogContact

Free, open-source tools for developers and creators · Community driven