{"id":131012,"date":"2026-05-04T13:09:54","date_gmt":"2026-05-04T13:09:54","guid":{"rendered":"https:\/\/softcrony.com\/blog\/?p=131012"},"modified":"2026-05-04T13:15:51","modified_gmt":"2026-05-04T13:15:51","slug":"docker-php-developers-guide","status":"publish","type":"post","link":"https:\/\/softcrony.com\/blog\/docker-php-developers-guide\/","title":{"rendered":"Docker for PHP Developers: Stop the &#8220;It Works on My Machine&#8221; Problem Forever"},"content":{"rendered":"<p>&#8220;It works perfectly on my machine.&#8221; These six words have caused more client crises, delayed more launches, and cost more money than almost any other problem in software development. Docker eliminates this entirely. Here&#8217;s how to use it as a PHP developer \u2014 even if you&#8217;ve never touched a container before.<\/p>\n<h2>The Problem Docker Solves<\/h2>\n<p>Without Docker, every developer on your team has a slightly different environment: different PHP version, different MySQL version, different extensions installed. Your local machine runs PHP 8.1, the client&#8217;s server runs PHP 7.4. Your colleague is on Windows, you&#8217;re on Mac. The staging server has a different timezone. Docker packages your entire environment \u2014 PHP version, extensions, web server, database \u2014 into a container that runs identically everywhere. Your local machine, staging server, and production server all run the exact same configuration.<\/p>\n<h2>Key Concepts in 2 Minutes<\/h2>\n<ul>\n<li><strong>Image:<\/strong> A blueprint for a container (like a PHP class before instantiation)<\/li>\n<li><strong>Container:<\/strong> A running instance of an image (the actual running object)<\/li>\n<li><strong>docker-compose.yml:<\/strong> A file that defines all your services (PHP, MySQL, Nginx) and how they connect<\/li>\n<li><strong>Volume:<\/strong> A way to share files between your local machine and the container so edits appear instantly<\/li>\n<\/ul>\n<h2>Step 1: Install Docker Desktop<\/h2>\n<p>Download Docker Desktop from <strong>docker.com<\/strong> for Mac or Windows. On Ubuntu Linux:<\/p>\n<pre><code>sudo apt-get update\r\nsudo apt-get install docker.io docker-compose\r\nsudo usermod -aG docker $USER\r\n# Log out and back in after this<\/code><\/pre>\n<p>Verify installation: <code>docker --version<\/code> and <code>docker-compose --version<\/code><\/p>\n<h2>Step 2: Create Your Project Structure<\/h2>\n<pre><code>my-project\/\r\n\u251c\u2500\u2500 docker-compose.yml\r\n\u251c\u2500\u2500 docker\/\r\n\u2502   \u251c\u2500\u2500 php\/\r\n\u2502   \u2502   \u2514\u2500\u2500 Dockerfile\r\n\u2502   \u2514\u2500\u2500 nginx\/\r\n\u2502       \u2514\u2500\u2500 default.conf\r\n\u251c\u2500\u2500 src\/           \u2190 your PHP code lives here\r\n\u2514\u2500\u2500 mysql\/         \u2190 database data (auto-created)<\/code><\/pre>\n<h2>Step 3: The docker-compose.yml File<\/h2>\n<p>Create <code>docker-compose.yml<\/code> in your project root:<\/p>\n<pre><code>version: '3.8'\r\n\r\nservices:\r\n\r\n  # PHP-FPM Service\r\n  php:\r\n    build:\r\n      context: .\/docker\/php\r\n      dockerfile: Dockerfile\r\n    container_name: php_app\r\n    volumes:\r\n      - .\/src:\/var\/www\/html\r\n    depends_on:\r\n      - mysql\r\n\r\n  # Nginx Web Server\r\n  nginx:\r\n    image: nginx:alpine\r\n    container_name: nginx_server\r\n    ports:\r\n      - \"8080:80\"\r\n    volumes:\r\n      - .\/src:\/var\/www\/html\r\n      - .\/docker\/nginx\/default.conf:\/etc\/nginx\/conf.d\/default.conf\r\n    depends_on:\r\n      - php\r\n\r\n  # MySQL Database\r\n  mysql:\r\n    image: mysql:8.0\r\n    container_name: mysql_db\r\n    environment:\r\n      MYSQL_ROOT_PASSWORD: rootpassword\r\n      MYSQL_DATABASE: myapp\r\n      MYSQL_USER: appuser\r\n      MYSQL_PASSWORD: apppassword\r\n    ports:\r\n      - \"3306:3306\"\r\n    volumes:\r\n      - .\/mysql:\/var\/lib\/mysql\r\n\r\n  # phpMyAdmin (optional but handy)\r\n  phpmyadmin:\r\n    image: phpmyadmin:latest\r\n    container_name: phpmyadmin\r\n    ports:\r\n      - \"8081:80\"\r\n    environment:\r\n      PMA_HOST: mysql\r\n      PMA_USER: appuser\r\n      PMA_PASSWORD: apppassword<\/code><\/pre>\n<h2>Step 4: Your PHP Dockerfile<\/h2>\n<p>Create <code>docker\/php\/Dockerfile<\/code>:<\/p>\n<pre><code>FROM php:8.2-fpm\r\n\r\n# Install common extensions\r\nRUN apt-get update &amp;&amp; apt-get install -y \\\r\n    libpng-dev \\\r\n    libonig-dev \\\r\n    libzip-dev \\\r\n    zip \\\r\n    unzip\r\n\r\nRUN docker-php-ext-install \\\r\n    pdo_mysql \\\r\n    mbstring \\\r\n    gd \\\r\n    zip \\\r\n    opcache\r\n\r\n# Install Composer\r\nCOPY --from=composer:latest \/usr\/bin\/composer \/usr\/bin\/composer\r\n\r\nWORKDIR \/var\/www\/html<\/code><\/pre>\n<h2>Step 5: Nginx Configuration<\/h2>\n<p>Create <code>docker\/nginx\/default.conf<\/code>:<\/p>\n<pre><code>server {\r\n    listen 80;\r\n    server_name localhost;\r\n    root \/var\/www\/html\/public;\r\n    index index.php index.html;\r\n\r\n    location \/ {\r\n        try_files $uri $uri\/ \/index.php?$query_string;\r\n    }\r\n\r\n    location ~ \\.php$ {\r\n        fastcgi_pass php:9000;\r\n        fastcgi_index index.php;\r\n        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;\r\n        include fastcgi_params;\r\n    }\r\n}<\/code><\/pre>\n<h2>Step 6: Start Your Environment<\/h2>\n<pre><code># Build and start all services (first time)\r\ndocker-compose up --build\r\n\r\n# Start in background after the first time\r\ndocker-compose up -d\r\n\r\n# Stop everything\r\ndocker-compose down\r\n\r\n# View logs\r\ndocker-compose logs -f php\r\n\r\n# Run a command inside the PHP container\r\ndocker-compose exec php bash\r\ndocker-compose exec php composer install<\/code><\/pre>\n<p>Visit <code>http:\/\/localhost:8080<\/code> \u2014 your PHP site is running. phpMyAdmin is at <code>http:\/\/localhost:8081<\/code>.<\/p>\n<h2>WordPress in Docker<\/h2>\n<p>For WordPress, simplify even further with the official image:<\/p>\n<pre><code>version: '3.8'\r\nservices:\r\n  wordpress:\r\n    image: wordpress:php8.2-apache\r\n    ports:\r\n      - \"8080:80\"\r\n    environment:\r\n      WORDPRESS_DB_HOST: mysql\r\n      WORDPRESS_DB_USER: wpuser\r\n      WORDPRESS_DB_PASSWORD: wppassword\r\n      WORDPRESS_DB_NAME: wordpress\r\n    volumes:\r\n      - .\/wp-content:\/var\/www\/html\/wp-content\r\n\r\n  mysql:\r\n    image: mysql:8.0\r\n    environment:\r\n      MYSQL_DATABASE: wordpress\r\n      MYSQL_USER: wpuser\r\n      MYSQL_PASSWORD: wppassword\r\n      MYSQL_ROOT_PASSWORD: rootpassword\r\n    volumes:\r\n      - mysql_data:\/var\/lib\/mysql\r\n\r\nvolumes:\r\n  mysql_data:<\/code><\/pre>\n<h2>Practical Tips for Teams<\/h2>\n<ul>\n<li><strong>Add docker-compose.yml to Git<\/strong> \u2014 every team member gets the same environment with one command<\/li>\n<li><strong>Add mysql\/ to .gitignore<\/strong> \u2014 never commit database files<\/li>\n<li><strong>Use .env files for passwords<\/strong> \u2014 reference them in docker-compose.yml with <code>${MYSQL_PASSWORD}<\/code><\/li>\n<li><strong>Use named volumes for databases<\/strong> \u2014 data persists even when containers are stopped<\/li>\n<\/ul>\n<h2>Moving to Production<\/h2>\n<p>Docker for development doesn&#8217;t automatically mean Docker in production \u2014 many teams develop with Docker but deploy to a standard cPanel\/VPS. That&#8217;s perfectly fine. The consistency benefit is still enormous. If you do want Docker in production, look at <strong>Docker Swarm<\/strong> (simpler) or <strong>Kubernetes<\/strong> (powerful, complex) for orchestrating multiple containers across servers. &#8212; <strong>We help development teams set up modern DevOps workflows.<\/strong> If your team is spending too much time on environment issues and manual deployments, <a href=\"https:\/\/softcrony.com\/contact\/\">talk to us \u2192<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>&#8220;It works perfectly on my machine.&#8221; These six words have caused more client crises, delayed more launches, and cost more money than almost any other problem in software development. Docker eliminates this entirely. Here&#8217;s how to use it as a PHP developer \u2014 even if you&#8217;ve never touched a container before. The Problem Docker Solves [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":131014,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[265],"tags":[274,268,272,275,273,8],"class_list":["post-131012","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-devops","tag-development-environment","tag-devops","tag-docker","tag-docker-compose","tag-php","tag-web-development"],"_links":{"self":[{"href":"https:\/\/softcrony.com\/blog\/wp-json\/wp\/v2\/posts\/131012","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/softcrony.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/softcrony.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/softcrony.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/softcrony.com\/blog\/wp-json\/wp\/v2\/comments?post=131012"}],"version-history":[{"count":2,"href":"https:\/\/softcrony.com\/blog\/wp-json\/wp\/v2\/posts\/131012\/revisions"}],"predecessor-version":[{"id":131034,"href":"https:\/\/softcrony.com\/blog\/wp-json\/wp\/v2\/posts\/131012\/revisions\/131034"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/softcrony.com\/blog\/wp-json\/wp\/v2\/media\/131014"}],"wp:attachment":[{"href":"https:\/\/softcrony.com\/blog\/wp-json\/wp\/v2\/media?parent=131012"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/softcrony.com\/blog\/wp-json\/wp\/v2\/categories?post=131012"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/softcrony.com\/blog\/wp-json\/wp\/v2\/tags?post=131012"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}