Md Nazmus Shakib
Back to articles
Technology

Solving "It Works on My Machine": Local Development with Docker, PHP, and Node.js

Md Nazmus Shakib

Md Nazmus Shakib

MERN Stack Developer

840m Total Read
Solving "It Works on My Machine": Local Development with Docker, PHP, and Node.js

"It works on my machine"—this single phrase represents thousands of lost hours in software engineering. When a team of developers collaborates on a modern full-stack application (e.g., a Next.js frontend, a PHP/Laravel backend, and a MySQL database), maintaining synchronized local environments natively on Windows, macOS, and Linux is virtually impossible. Version mismatches in Node.js or PHP extensions will inevitably cause builds to fail.

The permanent architectural solution to this chaos is Containerization. By leveraging Docker, we can encapsulate our entire technological stack into isolated, reproducible containers. In this technical guide, we will engineer a robust docker-compose architecture to orchestrate a unified local development environment spanning PHP, Node.js, and MySQL.

1. Structuring the Container Architecture

Before writing configuration files, you must establish a strict directory structure. We need to separate the backend application logic, the frontend application logic, and the Docker orchestration files to maintain modularity.

# Recommended Monorepo Directory Structure
/project-root
 ├── /frontend            # Next.js / React application
 ├── /backend             # PHP / Laravel application
 ├── /docker
 │    ├── /php            # Custom PHP Dockerfile & php.ini
 │    ├── /nginx          # Nginx server configurations
 │    └── /mysql          # Init SQL scripts
 └── docker-compose.yml   # The master orchestration file

2. Engineering the Custom PHP Environment

Standard official PHP images do not include the essential database extensions (like pdo_mysql) or package managers (Composer) required for modern backend development. We must define a custom Dockerfile for our PHP service to automate the installation of these dependencies.

docker/php/Dockerfile
# Base Image
FROM php:8.2-fpm-alpine

# Install necessary system libraries and PHP extensions
RUN apk add --no-cache libzip-dev zip unzip git curl \
    && docker-php-ext-install pdo_mysql zip

# Inject Composer directly from the official image
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

# Set the working directory for the container
WORKDIR /var/www/html

# Ensure proper permissions
RUN chown -R www-data:www-data /var/www/html
Architectural Warning: Alpine Linux

Always prefer Alpine-based images (e.g., php:8.2-fpm-alpine or node:20-alpine) for local development. They consume dramatically less RAM and disk space compared to standard Debian/Ubuntu-based images, preventing Docker from freezing your host machine.

3. Service Orchestration (docker-compose.yml)

The docker-compose.yml file is the blueprint of your architecture. It dictates how the Node.js frontend communicates with the PHP backend, mapping ports to your local host, and defining isolated bridge networks.

docker-compose.yml
version: '3.8'

services:
  # Next.js Frontend Service
  frontend:
    image: node:20-alpine
    working_dir: /app
    volumes:
      - ./frontend:/app
      - /app/node_modules # Isolate node_modules inside container
    ports:
      - "3000:3000"
    command: sh -c "npm install && npm run dev"
    networks:
      - app-network

  # PHP Backend Service (Using the custom Dockerfile)
  backend:
    build:
      context: .
      dockerfile: docker/php/Dockerfile
    volumes:
      - ./backend:/var/www/html
    ports:
      - "8000:8000"
    command: php -S 0.0.0.0:8000 -t public
    depends_on:
      - database
    networks:
      - app-network

  # MySQL Database Service
  database:
    image: mysql:8.0
    environment:
      MYSQL_DATABASE: my_application
      MYSQL_ROOT_PASSWORD: secret_root_password
      MYSQL_USER: dev_user
      MYSQL_PASSWORD: dev_password
    ports:
      - "3306:3306"
    volumes:
      - mysql_data:/var/lib/mysql
    networks:
      - app-network

# Define Docker Bridge Network
networks:
  app-network:
    driver: bridge

# Define Persistent Storage Volumes
volumes:
  mysql_data:

4. Enforcing Database Persistence

A fatal mistake beginners make with Docker is failing to configure Named Volumes for databases. Docker containers are ephemeral; if you run docker-compose down, the container is destroyed along with all data inside it. This means your MySQL database will be completely wiped out every time you restart your PC.

As demonstrated in the YAML configuration above, explicitly mapping mysql_data:/var/lib/mysql ensures that the raw SQL data files are stored on your host machine's physical drive, persisting through container destruction and recreation.

Conclusion

By standardizing your development stack with Docker Compose, you eradicate environment discrepancies across operating systems. A new developer joining your team does not need to spend hours installing PHP extensions or fighting Node version managers; they simply clone the repository, run docker-compose up -d, and begin writing code within a flawless, production-mirrored architecture.

#Docker#DevOps#PHP#Node.js#Next.js#MySQL#Containerization#Architecture
Md Nazmus Shakib
Written By

Md Nazmus Shakib

MERN Stack Developer

Specializing in the MERN stack and modern frameworks to engineer secure, SEO-optimized, and scalable digital solutions for visionary brands.

More about the author →