Deploy a PHP Application with MySQL Using Docker Compose

Introduction

When teams begin using Docker, they often face two common problems.
First, database data disappears after container restarts.
Second, applications sometimes fail to connect to databases.

Because of these issues, beginners often feel that Docker is unreliable.
However, the real problem is missing configuration, not Docker itself.

In this blog, we will solve both issues step by step.
We will deploy a PHP application with a MySQL database using Docker Compose, Docker volumes, and Docker networks.

Even if you are new to DevOps, you will be able to follow along.

Problem Statement

A company wants to deploy a simple PHP web application that stores and retrieves data from a MySQL database, but during testing, the team encounters several issues. The database data disappears whenever containers restart, and at times the PHP application fails to connect to MySQL. These problems arise because containers are temporary by nature, no persistent storage has been configured for the database, and the containers are not connected through a defined network. As a result, the application behaves unpredictably and does not meet reliability expectations for a production environment.


Why does this happen?

In the current setup, the MySQL data is stored directly inside the container, which is problematic because containers are ephemeral and can be destroyed and recreated at any time. Additionally, no persistent storage in the form of Docker volumes has been configured, and the containers are not connected using a defined network. Our task is to address all of these issues by using Docker Compose to deploy the PHP application with MySQL in a more reliable, persistent, and well-structured manner.


Solution Overview

We will:

  • Run PHP and MySQL in separate containers
  • Use Docker volumes to persist MySQL data
  • Use a Docker network for secure container communication
  • Use Docker Compose to manage everything together

Architecture Diagram (Conceptual)

Browser
   |
   v
PHP Container  --->  MySQL Container
   |                   |
   |               Docker Volume
   |               (Persistent Data)
   |
Docker Network

Project Structure

php-mysql-docker/
│
├── docker-compose.yml
└── php/
    └── index.php

Create the PHP Application

Create a folder named php and inside it create index.php.

php/index.php:

<?php
$host = getenv("DB_HOST");
$user = getenv("DB_USER");
$pass = getenv("DB_PASSWORD");
$db   = getenv("DB_NAME");

$conn = new mysqli($host, $user, $pass, $db);

if ($conn->connect_error) {
    die("Connection failed: " . $conn->connect_error);
}

$conn->query("CREATE TABLE IF NOT EXISTS users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100)
)");

if (isset($_POST['name'])) {
    $name = $_POST['name'];
    $conn->query("INSERT INTO users (name) VALUES ('$name')");
}

$result = $conn->query("SELECT * FROM users");
?>

<!DOCTYPE html>
<html>
<head>
    <title>PHP MySQL Docker</title>
</head>
<body>
<h2>Add User</h2>
<form method="post">
    <input type="text" name="name" required>
    <button type="submit">Save</button>
</form>

<h2>Saved Users</h2>
<ul>
<?php while($row = $result->fetch_assoc()): ?>
    <li><?php echo $row['name']; ?></li>
<?php endwhile; ?>
</ul>
</body>
</html>

Create Docker Compose Configuration

Create docker-compose.yml in the root directory.

docker-compose.yml:

version: '3.8'

services:
  php:
    image: php:8.1-apache
    container_name: php_app
    ports:
      - "8080:80"
    volumes:
      - ./php:/var/www/html
    environment:
      DB_HOST: mysql
      DB_USER: root
      DB_PASSWORD: rootpass
      DB_NAME: appdb
    depends_on:
      - mysql
    networks:
      - app_network

  mysql:
    image: mysql:8.0
    container_name: mysql_db
    environment:
      MYSQL_ROOT_PASSWORD: rootpass
      MYSQL_DATABASE: appdb
    volumes:
      - mysql_data:/var/lib/mysql
    networks:
      - app_network

volumes:
  mysql_data:

networks:
  app_network:

Why Docker Volumes Are Important

Docker volumes are important because they solve the problem of data persistence in containerized applications. Without volumes, MySQL data is stored inside the container itself, which means the data is lost as soon as the container is deleted or recreated. With Docker volumes, the data is stored outside the container on the host system, allowing containers to be stopped, restarted, or replaced without affecting the stored data. This separation of application lifecycle and data lifecycle is essential for building reliable and production-ready applications.

Here we use:

volumes:
  - mysql_data:/var/lib/mysql

Why Docker Networks Are Important

Docker networks allow containers to:

  • Communicate securely
  • Refer to each other using service names

In our PHP app:

$host = getenv("DB_HOST"); // mysql

The hostname mysql works because:

  • Both containers are on the same Docker network

Run the Application

From the project root directory, run:

docker-compose up -d

Check running containers:

docker ps

Access the Application

Open your browser:

http://localhost:8080

You should see:

  • A form to add users
  • A list of saved users

Verify Data Persistence

Add some names

Stop containers:

docker-compose down
  1. Start again:
docker-compose up -d

The data will still be there

This confirms Docker volume persistence


Verify Container Communication

Check PHP logs:

docker logs php_app

Check MySQL logs:

docker logs mysql_db

No connection errors = successful container networking 🎉


Common Beginner Mistakes (Avoid These)

Using localhost for DB connection
Not using volumes for databases
Hardcoding credentials
Running everything in one container


Expected Learning Outcomes

After completing this project, you’ll understand:

  • How Docker Compose manages multi-container applications
  • How Docker volumes prevent data loss
  • How Docker networks enable container communication
  • How real-world PHP + MySQL apps are deployed
  • How this setup works on AWS Free Tier

Conclusion

This project demonstrates a real-world DevOps scenario implemented using Docker Compose, where multiple services work together to form a complete application. By separating the application and database into independent services, adding persistent storage through Docker volumes, and defining dedicated networks for communication, the setup becomes far more robust and predictable. These practices ensure improved reliability by preventing data loss, scalability by allowing services to be managed and extended independently, and production-ready behavior that closely mirrors real deployment environments. For anyone preparing for DevOps interviews, this type of Docker Compose–based architecture is commonly discussed and frequently tested, making this project both practical and highly relevant.