Kritim Yantra
Sep 07, 2026
You open XAMPP, click Start beside Apache, then start MySQL.
For a second, everything looks fine.
Then MySQL turns red.
Error: MySQL shutdown unexpectedly.
You try again.
Same result.
You restart XAMPP. Run it as administrator. Restart Windows. Change a port. Copy some files. Search Stack Overflow. Maybe it works again for a few days.
Then one morning, the same error comes back.
I got tired of dealing with exactly this problem.
Instead of repeatedly trying to repair XAMPP's MySQL service, I changed my local development setup completely:
I still use XAMPP for Apache and PHP, but I run MySQL separately inside Docker.
And that small change made my local PHP development setup much less frustrating.
My setup now looks like this:
XAMPP
├── Apache
└── PHP
Docker
├── MySQL
├── phpMyAdmin
└── Persistent MySQL Volume
That's it.
No complicated Docker setup.
No need to move the PHP application into Docker.
No need to create a Dockerfile for PHP.
You can continue putting your PHP projects inside:
C:\xampp\htdocs\
and opening them normally through:
http://localhost/my-project
The only difference is that your PHP application connects to MySQL running inside Docker instead of the database server bundled with XAMPP.
In this guide, I'll show you exactly how.
There isn't one single reason behind every "MySQL shutdown unexpectedly" error.
It can happen because of things such as:
There is also something beginners often don't realize.
Although the XAMPP Control Panel displays the service as MySQL, modern XAMPP versions actually ship MariaDB instead of MySQL. Apache Friends says XAMPP switched from MySQL to MariaDB starting with XAMPP 5.5.30 and 5.6.14.
That isn't normally a problem because MySQL and MariaDB are highly compatible for common PHP development.
The problem for me wasn't whether the service was technically MySQL or MariaDB.
The problem was simpler:
I didn't want my database disappearing every time XAMPP decided it didn't want to start.
Instead of fixing the same XAMPP database problem again and again, I stopped using XAMPP for the database entirely.
I now use:
Apache Friends officially notes that you can use your own MySQL server with XAMPP; you simply shouldn't start XAMPP's bundled database server at the same time on the same port.
That makes this hybrid setup surprisingly simple.
[Suggested Diagram: XAMPP Apache/PHP connecting from Windows to MySQL running inside Docker, with phpMyAdmin connecting to the same MySQL container.]
The biggest reason is simple:
My database is no longer dependent on XAMPP's MySQL/MariaDB service.
When I open XAMPP, I only care about Apache.
My database has its own container and persistent Docker volume.
There are a few other benefits too.
Docker containers themselves shouldn't be treated as permanent storage.
That's why we attach a Docker volume to:
/var/lib/mysql
The MySQL files remain in the volume even when the MySQL container stops or is recreated.
Docker's current database documentation recommends this same pattern when running MySQL through Compose.
Think of the container as a rented office and the Docker volume as your filing cabinet.
You can close the office, reopen it, or replace it.
Your files remain in the cabinet.
This is probably my favorite part.
You don't have to Dockerize your entire PHP development environment.
Keep your projects inside:
C:\xampp\htdocs
Keep running Apache using XAMPP.
Keep opening:
http://localhost/project-name
Only your database moves to Docker.
For someone beginning with Docker, this is much easier than putting Apache, PHP, MySQL, Composer, and everything else into containers on day one.
Moving MySQL into Docker doesn't mean giving up phpMyAdmin.
We'll run phpMyAdmin in another container.
Both containers automatically share a Docker Compose network, allowing phpMyAdmin to reach MySQL using the database service name.
The official phpMyAdmin Docker image supports configuring the database server through PMA_HOST and PMA_PORT.
You'll simply open:
http://localhost:8080
and use phpMyAdmin almost exactly as you did before.
Before continuing, install:
You should also make sure Docker Desktop is running.
That's pretty much everything.
Open the XAMPP Control Panel.
Start:
Apache
But do not start MySQL.
Your control panel should effectively look like:
Apache Running
MySQL Stopped
From now on, XAMPP handles PHP and Apache while Docker handles MySQL.
Important: You generally don't want XAMPP's database server and Docker's MySQL trying to bind to the same Windows port.
Apache Friends specifically warns that two database servers can't start on the same port.
Create a folder anywhere you like.
For example:
C:\docker\mysql
Inside it, create:
docker-compose.yml
That's the main file we need.
You don't need a PHP Dockerfile.
You don't need an Apache Dockerfile.
You don't need to rebuild your PHP project.
Paste the following configuration into docker-compose.yml:
services:
mysql:
image: mysql:8.4
container_name: xampp-mysql
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: root_password
MYSQL_DATABASE: my_database
MYSQL_USER: my_user
MYSQL_PASSWORD: my_password
ports:
- "3307:3306"
volumes:
- mysql_data:/var/lib/mysql
phpmyadmin:
image: phpmyadmin:latest
container_name: xampp-phpmyadmin
restart: unless-stopped
depends_on:
- mysql
environment:
PMA_HOST: mysql
PMA_PORT: 3306
ports:
- "8080:80"
volumes:
mysql_data:
That's basically the entire setup.
The official MySQL Docker image currently provides the 8.4 tag, while Docker's database documentation demonstrates the same architecture of exposing MySQL on host port 3307, storing /var/lib/mysql in a named volume, and connecting phpMyAdmin to the database container.
Don't worry if Docker Compose looks strange at first.
Let's break it down.
mysql:
image: mysql:8.4
This tells Docker:
Start a container using MySQL 8.4.
MYSQL_ROOT_PASSWORD: root_password
This creates the MySQL root password during the initial database setup.
For local development, replace it with your preferred password.
Warning: Don't use simple credentials like these on a public or production server. This tutorial is intended for local development.
MYSQL_DATABASE: my_database
MySQL creates this database during the first initialization of an empty data directory.
You could rename it to something related to your project:
MYSQL_DATABASE: ecommerce
or:
MYSQL_DATABASE: krityemiyantra
MYSQL_USER: my_user
MYSQL_PASSWORD: my_password
Instead of connecting every PHP project using root, we create a normal application user.
That's a better habit.
Pay attention to this line:
ports:
- "3307:3306"
This means:
Windows Port Docker MySQL Port
3307 ---> 3306
MySQL continues listening on its normal port 3306 inside the container.
But Windows accesses it through:
3307
Docker's own MySQL + phpMyAdmin example uses this exact host-to-container port pattern.
Why do I like using 3307?
Because it keeps Docker MySQL clearly separate from any local database service that may already expect 3306.
You could technically use:
"3306:3306"
if nothing else is using 3306.
But using 3307 avoids unnecessary confusion and port conflicts.
Pro Tip: Use 3307 for Docker MySQL and forget about fighting over XAMPP's traditional database port.
Now look at:
volumes:
- mysql_data:/var/lib/mysql
and:
volumes:
mysql_data:
This creates a named Docker volume.
Your actual database data lives persistently inside that volume rather than relying only on the container's writable filesystem.
Docker's database guide specifically mounts a named volume at /var/lib/mysql for persistent MySQL data.
This is extremely important.
You can run:
docker compose down
and later:
docker compose up -d
Your databases should still be there because the named volume remains.
There is an important difference between:
docker compose down
and:
docker compose down -v
Be careful with:
docker compose down -v
The -v option tells Docker Compose to remove the project's named volumes as well.
That can delete the database storage associated with this Compose setup.
So for normal stopping and starting, use:
docker compose down
or simply:
docker compose stop
Don't casually delete your database volume.
Open PowerShell, Command Prompt, or your terminal inside the folder containing docker-compose.yml.
Run:
docker compose up -d
Docker will download the images if necessary and start both containers.
Check them with:
docker compose ps
You should see your MySQL and phpMyAdmin services running.
You can also use:
docker ps
Open your browser and visit:
http://localhost:8080
You should see phpMyAdmin.
Login using:
Username: root
Password: root_password
Or use the application user:
Username: my_user
Password: my_password
The phpMyAdmin container connects directly to:
mysql:3306
Why mysql?
Because mysql is the Compose service name:
mysql:
Docker Compose automatically provides networking between services in the same project, and containers can reach one another using the service name. Docker's documentation uses this same approach.
This confused me initially too, so here's the simple explanation.
phpMyAdmin is inside Docker.
PHP from XAMPP is outside Docker on Windows.
Therefore:
phpMyAdmin container
|
| mysql:3306
v
MySQL container
But your XAMPP PHP application connects like this:
XAMPP PHP
|
| 127.0.0.1:3307
v
Windows
|
| port mapping
v
Docker MySQL:3306
So remember this rule:
Inside Docker:
mysql:3306
From XAMPP PHP:
127.0.0.1:3307
Once you understand that, the entire setup becomes much easier.
[Suggested Diagram: Two connection paths showing phpMyAdmin → mysql:3306 and XAMPP PHP → 127.0.0.1:3307 → MySQL container.]
Suppose your PHP project is located here:
C:\xampp\htdocs\my-project
Instead of using:
localhost
with the default MySQL port, connect using:
Host: 127.0.0.1
Port: 3307
Database: my_database
Username: my_user
Password: my_password
<?php
$host = '127.0.0.1';
$port = 3307;
$database = 'my_database';
$username = 'my_user';
$password = 'my_password';
$conn = new mysqli(
$host,
$username,
$password,
$database,
$port
);
if ($conn->connect_error) {
die('Database connection failed: ' . $conn->connect_error);
}
echo 'Connected to Docker MySQL successfully!';
Save it as something like:
test-db.php
inside:
C:\xampp\htdocs\my-project\
Then open:
http://localhost/my-project/test-db.php
If everything is configured correctly, you'll see:
Connected to Docker MySQL successfully!
That PHP file is still being executed by XAMPP PHP.
The database connection simply goes to Docker.
If you prefer PDO:
<?php
$host = '127.0.0.1';
$port = 3307;
$database = 'my_database';
$username = 'my_user';
$password = 'my_password';
$dsn = "mysql:host={$host};port={$port};dbname={$database};charset=utf8mb4";
try {
$pdo = new PDO($dsn, $username, $password, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
echo 'Connected to Docker MySQL successfully!';
} catch (PDOException $e) {
die('Database connection failed: ' . $e->getMessage());
}
The important values are still:
127.0.0.1
3307
my_database
my_user
my_password
This is another reason I like this setup.
There aren't two databases.
phpMyAdmin and your PHP application are accessing the same MySQL server and the same Docker volume.
For example, create a table using phpMyAdmin:
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(150) NOT NULL
);
Then insert something from PHP.
Refresh phpMyAdmin.
The new data appears there.
That's because both applications are talking to the same MySQL container.
You probably don't want to lose an existing PHP project's database.
Before switching, export it.
If it still starts, open the old XAMPP phpMyAdmin.
Usually:
http://localhost/phpmyadmin
Select your database.
Choose:
Export → Quick → SQL → Export
Save the .sql file.
Stop XAMPP MySQL and start your Docker environment:
docker compose up -d
Visit:
http://localhost:8080
Select or create your database.
Choose:
Import
Upload your .sql file.
Now your old PHP project can use the migrated database through:
127.0.0.1:3307
Pro Tip: Always keep a separate database backup before migrating or experimenting with database storage.
After setting this up once, my normal workflow becomes:
Open XAMPP and start only:
Apache
Don't start MySQL.
From the folder containing your Compose file:
docker compose up -d
That's it.
Now you have:
PHP Website:
http://localhost/my-project
phpMyAdmin:
http://localhost:8080
MySQL:
127.0.0.1:3307
You have two choices.
You can leave the containers running.
Or stop them:
docker compose stop
Start them again later:
docker compose start
You can also use:
docker compose down
and recreate them later:
docker compose up -d
Because the database files live in the named volume, recreating the container doesn't mean starting from an empty database.
You really don't need many Docker commands.
docker compose up -d
docker compose ps
docker compose stop
docker compose start
docker compose down
docker compose logs mysql
The official MySQL image documentation also recommends Docker logs as a way to inspect the MySQL server's output.
If something isn't starting correctly, this command is much more useful than randomly changing files:
docker compose logs mysql
No.
That's another nice part of this setup.
You can ignore XAMPP's built-in phpMyAdmin and use:
http://localhost:8080
The Docker phpMyAdmin container connects directly to Docker MySQL.
The official phpMyAdmin Docker image supports this configuration through:
PMA_HOST: mysql
PMA_PORT: 3306
You absolutely can.
A fully containerized setup might include:
Nginx/Apache
PHP
MySQL
phpMyAdmin
Redis
inside Docker.
For larger projects, teams, or environments where consistency is critical, that can be a great setup.
But that isn't what I'm trying to solve here.
I wanted something simple.
I already liked using XAMPP for PHP development.
My frustration was the database shutdown problem.
So rather than rebuilding everything, I replaced only the part causing trouble.
Sometimes the simplest solution is the one you'll actually continue using.
Once you're using Docker MySQL, you generally don't need to start XAMPP's database service.
Your workflow becomes:
XAMPP:
Apache = ON
MySQL = OFF
Docker:
MySQL = ON
phpMyAdmin = ON
Keep it simple.
Remember that the Compose file exposes MySQL on Windows through:
3307
So this may fail:
$port = 3306;
Use:
$port = 3307;
for PHP running directly through XAMPP.
mysql as the Host in XAMPP PHPThis:
$host = 'mysql';
works for another container on the same Docker Compose network.
Your XAMPP PHP installation isn't inside that network.
Use:
$host = '127.0.0.1';
127.0.0.1:3307 for Docker phpMyAdminphpMyAdmin is already inside Docker.
It should connect directly to:
mysql:3306
That's why our Compose file contains:
PMA_HOST: mysql
PMA_PORT: 3306
docker compose down -v Without Knowing What It DoesThis deserves repeating.
Don't casually run:
docker compose down -v
when your database is important.
The volume is where your MySQL data persists.
Keep backups of anything you can't afford to lose.
MYSQL_DATABASE and Expecting an Existing Volume to ResetThis catches beginners surprisingly often.
Environment variables such as:
MYSQL_DATABASE
MYSQL_USER
MYSQL_PASSWORD
are primarily used when MySQL initializes an empty data directory.
The official MySQL Docker image documentation notes that initialization variables don't reinitialize a database directory that already contains a database.
So if your volume already contains MySQL data, simply changing:
MYSQL_DATABASE: new_database
doesn't mean Docker will wipe everything and recreate the server.
And that's a good thing.
Here's how I think about it.
This doesn't magically repair every possible cause of XAMPP's "MySQL shutdown unexpectedly" error.
Instead, it removes that database service from my development workflow.
I no longer depend on XAMPP's bundled database server.
So for me, that recurring XAMPP MySQL shutdown problem effectively stopped mattering.
Could Docker MySQL itself have a problem one day?
Of course.
No software is guaranteed never to fail.
But the database is now isolated, reproducible through one Compose file, easier to inspect through container logs, and stored in a persistent Docker volume.
That's a much cleaner workflow for me.
This is the part I really like.
Your database development environment can basically be represented by this single file:
services:
mysql:
image: mysql:8.4
container_name: xampp-mysql
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: root_password
MYSQL_DATABASE: my_database
MYSQL_USER: my_user
MYSQL_PASSWORD: my_password
ports:
- "3307:3306"
volumes:
- mysql_data:/var/lib/mysql
phpmyadmin:
image: phpmyadmin:latest
container_name: xampp-phpmyadmin
restart: unless-stopped
depends_on:
- mysql
environment:
PMA_HOST: mysql
PMA_PORT: 3306
ports:
- "8080:80"
volumes:
mysql_data:
Then:
docker compose up -d
And you're ready.
Your PHP remains inside XAMPP.
Your database runs through Docker.
Your database files persist in a Docker volume.
phpMyAdmin runs at port 8080.
Your PHP application connects through port 3307.
Simple.
If you've searched for the XAMPP MySQL shutdown unexpectedly error multiple times, you probably understand the frustration.
You fix it.
It works.
Then eventually you're looking at the same red error message again.
That was the point where I decided I didn't want to keep fixing the same part of my development environment.
I still use XAMPP because it makes running Apache and PHP incredibly convenient.
I simply stopped asking XAMPP to manage my database.
Now my setup is:
XAMPP = Apache + PHP
Docker = MySQL + phpMyAdmin + Database Volume
For local PHP development, I've found this combination simple, understandable, and much less frustrating.
If you're currently fighting with the XAMPP MySQL shutdown error, back up your existing database and try moving only MySQL to Docker.
You don't need to Dockerize your entire application.
Start with the part that's causing you problems.
Have you also experienced XAMPP's "MySQL shutdown unexpectedly" error? What solution have you been using to deal with it?
There isn't one fix for every cause of the error. One reliable alternative is to stop using XAMPP's bundled database service and run MySQL separately in Docker while continuing to use XAMPP for Apache and PHP.
Your XAMPP PHP application can then connect to Docker MySQL through 127.0.0.1:3307.
Yes.
Your PHP application can continue running normally through XAMPP while connecting to a MySQL container exposed on your Windows machine.
For the configuration used in this tutorial:
Host: 127.0.0.1
Port: 3307
Apache Friends also confirms that XAMPP can be used with your own database server as long as you avoid database services competing for the same port.
Not if you configure persistent storage correctly.
This tutorial attaches a named Docker volume:
mysql_data:/var/lib/mysql
That volume stores the MySQL database files independently from the normal lifecycle of the container.
Still, keep proper backups of important databases.
Yes.
The official phpMyAdmin Docker image can connect to another database container by setting variables such as:
PMA_HOST: mysql
PMA_PORT: 3306
In this setup, you access it through:
http://localhost:8080
Your XAMPP PHP application runs on Windows outside Docker, so it accesses the exposed host port:
127.0.0.1:3307
phpMyAdmin runs inside the same Docker Compose network as MySQL, so it connects directly to:
mysql:3306
No.
That's the whole idea behind this setup.
You can continue using XAMPP for:
Apache + PHP
and use Docker only for:
MySQL + phpMyAdmin
It's a good way to start using Docker without completely changing your existing PHP workflow.
No comments yet. Be the first to comment!
Please log in to post a comment:
Sign in with Google
Kritim Yantra